Carga de librerías a utilizar

library(tidyverse)
library(tidytext)
library(tm)
library(textstem)
library(patchwork)
library(wordcloud) 
library(plotly)
library(topicmodels)
library(tictoc)
library(cowplot)
library(reshape2)
library(stm)
library(textrecipes)
library(textclean)
library(tidymodels)

Etapa 1: carga de datos y preprocesamiento

El archivo que se adjunta consiste en un corpus de unas 7.000 noticias scrapeadas entre julio y septiembre de 2019 de los siguientes medios de circulación nacional:

Télam La Nación Clarín Perfil Infobae MinutoUno Página 12

Constituye una muestra aleatoria del corpus construido por Florencia Piñeyrúa para su tesina de grado “Procesamiento del lenguaje natural aplicado al estudio de tópicos de noticias de seguridad en Argentina: julio a septiembre 2019”. Una exposición más concentrada de sus resultados puede encontrarse en el siguiente artículo.

El corpus contiene las siguientes variables:

id : identificador de cada documento url : link a la noticia original fecha : fecha de publicación anio : año de publicación mes : mes de publicación dia : dia de publicación medio : medio en el que fue publicado orientacion: clasificación -provisoria- de los medios según su línea editorial predominante (más conservador, más progresista, neutral) titulo texto

Carga dataset

corpus_base <- read_csv("M5_corpus_medios.csv") 
Rows: 7000 Columns: 10── Column specification ────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
chr  (5): url, medio, orientacion, titulo, texto
dbl  (4): id, anio, mes, dia
date (1): fecha
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
# En este apartado vemos cuántas noticias aporta cada medio al corpus y calculamos la proporción sobre el total.

    corpus_base %>%
        group_by(medio) %>%
        summarise(n=n()) %>%
        mutate(
                total = sum(n),
                prop = n/total*100
                ) %>%
        ungroup() %>%
        select(medio, n, prop) %>% 
        arrange(desc(n))

Normalización y tokenización

corpus_tidy <- corpus_base %>% 
    mutate(texto= stringi::stri_trans_general(texto, "Latin-ASCII"),
         titulo = stringi::stri_trans_general(titulo, "Latin-ASCII")) %>% 
    mutate(texto = str_replace_all(texto, '[[:digit:]]+', '')) %>% 
    unnest_tokens(word, texto, to_lower = TRUE)

# En este este código tomamos el corpus de texto "corpus_base", lo normalizamos convirtiendo el texto y el título a caracteres ASCII, eliminamos los dígitos del texto y finalmente los dividimos en tokens para su posterior análisis.

Eliminar stopwords

stop_words_1 <- read_csv('https://raw.githubusercontent.com/Alir3z4/stop-words/master/spanish.txt', col_names=FALSE) %>%
        rename(word = X1) %>%
        mutate(word = stringi::stri_trans_general(word, "Latin-ASCII"))
Rows: 608 Columns: 1── Column specification ────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
chr (1): X1
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
# En este código leemos un archivo CSV de stop words en español ubicado en la URL especificada, renombramos la columna a "word" y luego transformamos las palabras en la columna para asegurarnos de que estén en formato ASCII. El resultado final es un dataframe llamado "stop_words_1" que contiene la lista de stopwords normalizadas.

stop_words_2 <- read_csv("z_stopwords.txt", col_names = FALSE) %>% 
  rename(word = X1) %>% 
   mutate(word=stringi::stri_trans_general(word, "Latin-ASCII"))
Rows: 732 Columns: 1── Column specification ────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
chr (1): X1
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
# Ídem pero el archivo CSV llamado "z_stopwords.txt" se carga desde la carpeta.

stop_words_full <- stop_words_1 %>% 
  bind_rows(stop_words_2) %>% 
  distinct()

# En este paso combinamos los dos dataframes de stopwords, eliminamos las filas duplicadas y guardamos el resultado en un nuevo dataframe llamado "stop_words_full". Este dataframe contiene la lista completa y única de stopwords normalizadas.

corpus_tidy <- corpus_tidy %>% 
  anti_join(stop_words_full)
Joining with `by = join_by(word)`
# En este código eliminamos las stopwords del corpus de texto "corpus_tidy" utilizando la lista de stopwords contenida en el dataframe "stop_words_full". Este paso en el procesamiento de texto es necesario para eliminar palabras que no aportan significado para el análisis posterior.

Corrección “años”

corpus_tidy <- corpus_tidy %>% 
  mutate(word = case_when(
    word == 'ano' ~ 'anio',
    word == 'anos' ~ 'anio',
    TRUE ~ word
  ))

Etapa 2: Consignas

¿Cuáles son las palabras más utilizadas en cada uno de los medios? ¿Pueden verse diferencias? (Tener en cuenta las diferentes métricas trabajadas en el curso: tf, tf-idf, etc.) Generar las visualizaciones que considere más pertinentes para responder la pregunta.

Exploración palabras más frecuentes

Comenzamos explorando el corpus en formato tidy para identificar rápidamente algunos de los términos más frecuentes por medio.

corpus_tidy %>%
        group_by(word, medio) %>%
        summarise(n=n()) %>%
        arrange(desc(n))
`summarise()` has grouped output by 'word'. You can override using the `.groups` argument.
# Mediante este código agrupamos las palabras únicas en el corpus, contamos el número de ocurrencias de cada palabra y las ordenamos en función de su frecuencia de aparición, mostrando primero las palabras más frecuentes.

corpus_tidy %>%
  filter(medio == 'infobae')  %>% 
  group_by(word) %>%
  summarise(n=n()) %>%
  arrange(desc(n))
        
# Mirada rápida a las palabras más frecuentes de algunos medios

corpus_tidy %>%
        group_by(medio, word) %>%
        summarise(n=n()) %>%
        arrange(desc(n)) %>%
        pivot_wider(names_from = medio,
                    values_from = n)
`summarise()` has grouped output by 'medio'. You can override using the `.groups` argument.
# Este código calcula el recuento de ocurrencias de cada palabra en el corpus de texto agrupado según el medio de comunicación, y luego reorganiza estos resultados en un formato más ancho donde cada medio tiene sus recuentos de ocurrencias asociados para cada palabra.
word_counts_10 <- corpus_tidy %>%
        group_by(medio, word) %>%
        summarise(n=n()) %>%
        slice_max(n, n = 10) %>% 
        ungroup()
`summarise()` has grouped output by 'medio'. You can override using the `.groups` argument.
# En este paso calculamos el recuento de ocurrencias de cada palabra en el corpus, agrupadas por la columna "medio", y almacena estos recuentos en un nuevo dataframe llamado "word_counts" para pasarle a la función de graficado.

word_counts_all_10 <- corpus_tidy %>%
        group_by(word) %>%
        summarise(n=n()) %>%
        slice_max(n, n = 10) %>% 
        ungroup() %>% 
        mutate(medio = 'general')

# Aquí calculamos el recuento de ocurrencias de cada palabra en el corpus, sin agrupar por medio. Luego, seleccionamos las 10 palabras más comunes en todo el corpus. A estos resultados les agregamos la etiqueta "general" en la columna "medio" para poder comparar visualmente la distribución general con la distribución por medio

word_counts_top_10 <- word_counts_10 %>% 
  bind_rows(word_counts_all_10)

# Unifico en un dataset

A continuación creamos una función para automatizar todas las visualizaciones relacionadas a las métricas del corpus.

crear_graf_words <- function(data) {
  medios <- unique(data$medio)
  graficos <- list()
  
  for (medio_actual in medios) {
    datos_medio <- data %>%
      filter(medio == medio_actual) %>%
      mutate(word = fct_reorder(word, n))
    
    grafico <- ggplot(datos_medio, aes(n, word)) +
                geom_col() +
                geom_text(aes(label = n), position = position_stack(vjust = 0.5), family = "Courier", color = "white") +
                labs(title = medio_actual,
                     x = "Frecuencia",
                     y = "Palabra") +
                theme_classic()+
                theme(plot.title = element_text(hjust= 0.5),
                      axis.title = element_blank(),
                      axis.ticks.x = element_blank(),
                      text = element_text(family = "Courier"))
    
    graficos[[medio_actual]] <- grafico
  }
  
  wrap_plots(graficos)
}

# Esta función crea para cada medio un gráfico con el top 10 de palabras según cada métrica analizada

El eje x muestra las palabras y el eje y muestra la frecuencia de cada palabra. El gráfico está segmentado en paneles por cada medio, y las escalas del eje y se ajustan para cada panel individual.

crear_graf_words(word_counts_top_10)


# Este código genera una visualización de las palabras más comunes en el corpus, desglosadas por medio y en general. Las palabras más comunes se muestran en orden descendente de frecuencia en cada categoría.

Algunas observaciones preliminares:

  • Clarín e Infobae aportan cada uno el 22% de las noticias al corpus. El peso de las palabras más importantes para estos medios en el ranking general es alto.

  • Año parece una stopword porque es una palabra que presenta una frecuencia alta intra e inter medios.

  • Crónica, LN e Infobae son los únicos que no incluyen nombres propios entre las palabras más frecuentes.

  • En LN hay muchas referencias a redes sociales y acciones en ellas (guardar, compartir). Las acciones y “fuente” podrían ser stopwords. Incluso las menciones a RRSS podrían ser producto del scrapping y no del contenido de las notas.

  • Macri aparece como el político más nombrado. Lógicamente, es un resultado esperable si se tiene en cuenta que las notas relevadas corresponden al último año de su gobierno.

  • Infobae y sobre todo Crónica parecen cubrir temas más generales que los demás medios, al incluir palabras como “polícía”, “ciudad”, “mujer”, “hombre”, “casa”, “personas”, “mundo”, “vida”. Incluso, la frecuencia de la palabra “policía” en Crónica (#2 o #1 si se excluye el término “anio”) podría indicar que la mayor parte de la cobertura de este medio se orienta a la temática policial. De todas formas, en el top 10 también aparecen palabras como “gobierno” y “presidente”.

  • La palabra “frente” aparece con frecuencia en varios medios, lo que nos inclina a corroborar si refiere a frentes electorales o simplemente a un adverbio de lugar y, en este caso, debería considerarse como stopword.

  • A modo de síntesis, podría advertirse que las palabras con mayor frecuencia de aparición en la totalidad de los medios se corresponden con la temática política. Es probable que esta vinculación sea un desprendimiento del escenario electoral que signó el período bajo análisis.

Term Frequency - TF:

A modo de práctica generamos el cálculo de term frequency manualmente para visualizar la distribución de términos según su frecuencia respecto al total de términos, aperturado por medio.

# La "term frequency" (frecuencia de término) es una métrica para evaluar la importancia de una palabra en un documento o corpus de documentos, a partir de la medición de la frecuencia de su aparición en comparación con el número total de palabras.

words_tidy <- corpus_tidy %>% 
  group_by(medio, word) %>% # en función de la consigna, hacemos el conteo de términos por medio
  summarise(n=n()) %>% 
  arrange(desc(n)) 
`summarise()` has grouped output by 'medio'. You can override using the `.groups` argument.
total_words <- words_tidy %>%
        group_by(medio) %>%
        summarize(total = sum(n)) 

words_tidy <- words_tidy %>% 
  left_join(total_words) %>% 
  ungroup() %>% 
  arrange(desc(n))
Joining with `by = join_by(medio)`
# En este código contamos la frecuencia de cada palabra en el corpus del medio y también el total de palabras en el medio. Con estos datos podemos calcular a continuación la importancia de cada término en el medio.

En esta visualización podemos interactuar para identificar la cantidad de términos por tf:

tf_viz <- words_tidy %>% mutate(tf = n/total) %>%
          ggplot(aes(tf)) +
                geom_histogram(show.legend = FALSE) +
                coord_flip()+
                xlim(NA, 0.0002) +
                facet_wrap(~medio) +
                theme_classic()+
                theme(plot.title = element_text(hjust= 0.5),
                      axis.title = element_blank(),
                      axis.ticks.x = element_blank(),
                      text = element_text(family = "Courier"))
                  
ggplotly(tf_viz)
`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.Warning: Removed 6859 rows containing non-finite values (`stat_bin()`).

Vemos que para los medios más masivos se cumple la Ley de Zipf: pocas palabras ocurren muchas veces. Los corpus de Crónica, Minuto Uno y Télam tienen menos términos que los demás y la tf tiene un rango menor (uso de palabras mejor distribuido).

Term Frequency - Inverse Document Frequency (TF-IDF)

En esta etapa complementamos el análisis con las métricas de idf y tf_idf, para obtener una medida más completa de la importancia de los términos en el corpus de documentos.

tf_idf <- words_tidy %>% 
  bind_tf_idf(word, medio, n) 

tf_idf %>% select(-total) %>% 
  arrange(desc(tf_idf))

# En este código calculamos las métricas tf, idf y tf_idf para todos los términos del corpus

Creamos tres dataset, uno por métrica, con el top ten de términos para pasarle a la función de visualización.

tf_10 <- tf_idf %>%
        group_by(medio) %>%
        slice_max(tf, n = 10) %>% 
        select(medio, word, tf) %>% 
        rename(n = tf) %>%
        mutate(n = log(n + 1) * 100) %>% 
        mutate(n = round(n, 4))

idf_10 <- tf_idf %>%
          group_by(medio) %>%
          arrange(desc(idf)) %>%
          slice_head(n = 10) %>%
          select(medio, word, idf) %>%
          rename(n = idf) %>%
          mutate(n = log(n + 1) * 100) %>% 
          mutate(n = round(n, 4))

tf_idf_10 <- tf_idf %>%
        group_by(medio) %>%
        arrange(desc(idf)) %>%
        slice_head(n = 10) %>% 
        select(medio, word, tf_idf) %>% 
        rename(n = tf_idf) %>%
        mutate(n = log(n + 1) * 100) %>% 
        mutate(n = round(n, 4))

Pasamos cada uno de los top ten de términos por la función de graficado para visualizar los términos más importantes según cada métrica.

crear_graf_words(tf_10)

crear_graf_words(idf_10)

crear_graf_words(tf_idf_10)

Análisis preliminar de TF-IDF:

  • En primer lugar identificamos la necesidad de agregar palabras al listado de stopwords. Entendemos que no aportan información sobre el contenido u orientación de las noticias y a su vez son únicos para algunos medios y eso genera un alto tf_idf.

    Ejemplos: jpe, ap, emj, cronica.com.ar, fvazquez, cronicavirales, hd, pemex, gt, afv, jpg, minutouno.com, ambito.com, ivanovich, loading, paginai, protected, lxs, email, r.c,cp, fel,l.l,d.s, ea, f.f, f.d.s, fh, a.g, pct, telam.la, nacional.el

  • Podemos identificar “firmas” de periodistas (nombres de usuario o siglas) que tampoco son informativas sobre el contenido del corpus.

Etapa 1.2: Ampliación stopwords y reprocesamiento de la base

El análisis realizado nos permitió encontrar numerosas palabras incluidas en el corpus que creemos son producto del scrapping. Previo a la modelización para identificar tópicos creemos pertinente ampliar el listado de stopwords y repetir el preprocesamiento de los datos.

Para ampliar el listado de stopwords partimos del listado de palabras con sus métricas de tf e idf.

# Mediante el siguiente código eliminamos palabras que incluyen puntos y parecen producto de scrapping web. 
nuevas_stopwords_1 <- tf_idf %>% 
  select(word) %>% 
  filter(grepl('\\.', word)) %>% 
  pull(word)

A partir de la nueva lista de stopwords, recreamos el corpus en formato tidy y también el dataframe en formato medio/término/n para continuar con el análisis de métricas.

corpus_tidy_v2 <- corpus_tidy %>% 
  anti_join(stop_words_full_v2)
Joining with `by = join_by(word)`
words_tidy_v2 <- corpus_tidy_v2 %>% 
  group_by(medio, word) %>%
  summarise(n=n())
`summarise()` has grouped output by 'medio'. You can override using the `.groups` argument.
total_words_v2 <- words_tidy_v2 %>%
        group_by(medio) %>%
        summarize(total = sum(n)) 

words_tidy_v2 <- words_tidy_v2 %>% 
  left_join(total_words_v2) %>% 
  ungroup() %>% 
  arrange(desc(n))
Joining with `by = join_by(medio)`

Revisión de métricas

Creamos el gráfico con la distribución de términos según su tf para identificar variaciones respecto a la v1.

Creamos los dataset necesarios para visualizar los principales términos por métrica y por medio, buscando diferencias respecto a la v1.

Análisis final de TF-IDF:

## Modelado de tópicos: LDA
¿Cuáles son los tópicos principales en el corpus? ¿Pueden evidenciar diferencias en cada uno de los medios? Explicar qué método se utilizó para responder la pregunta, cuáles son los supuestos del mismo. Generar las visualizaciones más adecuadas para responder a las preguntas.
Para implementar un modelado de temas con LDA necesitamos construir una matriz DTM.
r para_disc_dtm <- corpus_tidy_v2 %>% group_by(id, word) %>% summarise(n=n())
`summarise()` has grouped output by 'id'. You can override using the `.groups` argument.
```r View(corpus_base) # creo un conteo de palabras por noticia, no por medio
disc_dtm_2 <- para_disc_dtm %>% cast_dtm(id, word, n)
```
En primer lugar intentamos detectar 5 tópicos:
```r lda_5 <- LDA(disc_dtm_2, k = 5, control = list(seed = 555))
write_rds(lda_5,“modelos/lda_5.rds”)
lda_5 <- read_rds(“modelos/lda_5.rds”)
ap_topics_5 <- tidy(lda_5, matrix = “beta”)
ap_topics_5 %>% mutate(beta = round(100*beta,6)) ```
```r
ap_top_terms_5 <- ap_topics_5 %>% group_by(topic) %>% slice_max(beta, n = 15) %>% ungroup() %>% arrange(topic, -beta)
ap_top_terms_5 %>% mutate(term = reorder_within(term, beta, topic)) %>% ggplot(aes(beta, term, fill = factor(topic))) + geom_col(show.legend = FALSE) + facet_wrap(~ topic, scales=‘free_y’) + scale_y_reordered()+ theme_minimal()+ labs(title = “Palabras más importantes en 5 tópicos LDA”) ```
r NA
La exploración de términos por tópico según el beta (probabilidad de pertenencia a un tópico) permite asociar temáticas a cada uno de ellos:
1- Política Nacional/ Elecciones 2- Deportes 3- Sociedad 4- Política Internacional 5- Economía
A continuación, hacemos una nueva prueba para explorar si es posible identificar una mayor cantidad de tópicos (k=10):
Con esta prueba, que duplicó la cantidad de tópicos, comenzamos a registrar conjuntos que no tienen un sentido tan definido, catalogados provisoriamente como tópicos de “Sociedad” (1, 3 y 6). A su vez en dos casos es posible reconocer una superposición temática (4 y 8).
1- Sociedad 2- Política Nacional/ Elecciones 3- Sociedad (¿Vida Familiar?) 4- Política Internacional 5- Policiales 6- Sociedad 7- Deporte 8- Política Internacional 9- Economía 10- Cultura/ Cine y Series
En esa dirección, avanzamos en una nueva prueba que propone reducir levemente la cantidad de tópicos (k=8):
En comparación con la prueba anterior, también detectamos tres tópicos con un sentido difuso (1, 3 y 7), mientras que desaparecieron tanto la repetición del tópico “Política Internacional” como el tópico “Cultura/ Cine y Series”.
1- Sociedad 2- Economía 3- Sociedad 4- Policiales 5- Política Internacional 6- Deportes 7- Sociedad (¿Y Cultura?) 8- Política Nacional/ Elecciones
Teniendo en cuenta las tres pruebas realizadas con el modelo LDA, entendemos que el mejor modelo es el que plantea un K=5. La visualización correspondiente a k=5 muestra que algunas palabras como “argentina” o “personas” son comunes a más de un tema. Es decir, que los tópicos identificados tienen cierta superposición en términos de palabras. Como alternativa podríamos considerar los términos que tuvieran la mayor diferencia en β entre el tema 1 y el tema 5 (que son dos de los que mejor podemos interpretar).
DUDA: en el ejemplo que aparece en la notebook, hay dos temas que no tienen un sentido definido y por eso “Esto parece un primer indicador de que deberíamos considerar la posibilidad de utilizar un número de tópicos más elevado”. En nuestro caso lo estoy pensando al revés (menos k, mayor definición de tópicos).
Palabras como “vidal”, “justicia” o “kirchner” caracterizan al tópico 1 (Política Nacional), mientras que “productos”, “inflación” o “mercado” representan al tópico 5 (Economía). Esto contribuye a confirmar que se trata de dos tópicos bien diferenciados por el modelo.
Ya identificados 5 tópicos y confirmada la diferenciación entre ellos podemos utilizar los resultados del modelo para relacionar cada noticia con un tópico. La matriz gamma establece la probabilidad de cada documento a pertenecer a un tópico. En particular nos sirve para asociar tópicos con cada uno de los medios a los que pertenecen las noticias.
Cada uno de estos valores es una proporción estimada de palabras de ese documento que se generan a partir de ese tema. En busca de validar que el modelo identifico claramente un tópico asociado a Política Nacional, vamos a revisar el documento que mayor probabilidad tiene de expresarlo.
En particular, si observamos el documento 94, la probabilidad gamma asociada al tema 1 es 0.99878, lo que significa que el modelo estima que alrededor del 99% de las palabras en el documento 94 se generaron a partir del tema 1. Por lo tanto, podemos concluir que el tema 1 es altamente relevante para el documento 94 según el modelo.
Se ve como en esta noticia parecen predominar palabras del tópico 1 (“nacional”, “elecciones”). Veamos el texto completo de este documento:
La noticia (id=94) habla de las elecciones y, en particular, de las elecciones en la provincia de Mendoza.
A continuación utilizamos la matriz de tópicos por documento para joinear con el medio al que pertenece la noticia y finalmente visualizar la predominancia de tópicos por medio:
Resultados (interpretación a desarrollar) Es posible reconocer diferencias en la prevalencia de los tópicos para cada uno de los medios, con excepción del tópico “Economía” que muestra una prevalencia similar en cuatro medios.
1- Política Nacional/Elecciones = Télam 2- Deportes = Crónica 3- Sociedad = La Nación 4- Política Internacional = Infobae 5- Economía = Télam, Página 12, Perfil y Clarín
## Modelado de tópicos: STM
Para implementar un modelado de temas con STM necesitamos construir una matriz DFM.
r para_disc_dfm <- corpus_tidy_v2 %>% group_by(id, word) %>% summarise(n=n())
`summarise()` has grouped output by 'id'. You can override using the `.groups` argument.
```r disc_dfm <- para_disc_dfm %>% cast_dfm(id, word, n)
disc_dfm ```
Document-feature matrix of: 6,999 documents, 96,403 features (99.81% sparse) and 0 docvars. features docs abrir abrirse acceso aceptara aclaran acomodamiento acreedores actitud actividad activos 8 1 1 1 1 1 1 2 2 1 2 30 0 0 0 0 0 0 0 0 0 0 31 0 0 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 43 0 0 0 0 0 0 0 0 0 0 66 0 0 0 0 0 0 0 0 0 0 [ reached max_ndoc ... 6,993 more documents, reached max_nfeat ... 96,393 more features ]
A diferencia del modelo LDA, STM permite incorporar metadata que podría influenciar la detección de tópicos. En este caso sumamos como covariable para cada documento a qué medio pertenece.
r metadata <- corpus_tidy_v2 %>% select(id, medio) %>% distinct() %>% left_join(corpus_base %>% select(id, texto))
Joining with `by = join_by(id)`
Al igual que con LDA, comenzamos pidiendole al modelo que encuentre 5 tópicos en el corpus:
Vamos a generar dos matrices para explorar los resultados del modelo: palabras por tópico y documentos por tópico
De manera similar al LDA, podemos identificar tópicos asociados a Política Nacional, a Deportes, a Economía y a Sociedad. No distingue contenidos relacionados a política internacional pero aparece un tópico judicial/policial.
1- Política Nacional/Elecciones 2- Economía 3- Justicia/Policial 4- Sociedad 5- Deportes
A modo de prueba, y considerando que STM pudo identificar un nuevo tópico, corremos el modelo con un k mayor.
r stm_8 <- read_rds("modelos/stm_8.rds")
El modelo STM con k = 8 parece tener mayor éxito que LDA para detectar tópicos claros:
1. Política internacional 2. Políticas de Educación, Salud, Desarrollo Social 3. Policiales y Justicia 4. Sociedad y cultura 5. Deportes 6. Política Nacional/Elecciones 7. Economía 8. Clima y geografía
Dado su éxito hasta acá, hacemos la prueba con 10 tópicos:
Notamos que los tópicos 7 y 10 se vuelven similares entre sí, ambos parecen estar relacionados a política interncional. Aunque incorpora un tópico relacionado a medicina y salud puede verse que los términos asociados a él tienen un beta bajo, dando cuenta de la baja potencialidad de esta temática en el contenido analizado.
Dados los resultados hasta acá del modelo STM, vamos a optar por quedarnos con un k=8 ya que muestra tópicos con mayor definición. Al igual que en LDA vamos a validar el tópico de mayor relevancia para este análisis, el número 6 sobre Política Nacional/Elecciones. Con la función labelTopics identificamos que las palabras con mayor probabilidad de pertenecer al tópico hacen clara alusión a los principales candidatos presidenciales mientras que los términos que más distinguen el tema son también apellidos de candidatos políticos.
A continuación vamos a buscar la predominancia de tópicos por documentos
En este paso notamos que al crear la matriz tópico-documento, la función tidy pisa los id de cada noticia, generando que el posterior join con la metadata para identificar el medio, no funcione. No pudimos resolverlo. Por ende entendemos que el análisis de tópicos STM por noticia no es válido.
Podemos encontrar predominancia de tópicos por medio: 1. Política internacional = Página 12 2. Políticas de Educación, Salud, Desarrollo Social = Clarín 3. Policiales y Justicia = Crónica y Página 12 4. Sociedad y cultura = Página 12 5. Deportes = Télam 6. Política Nacional/Elecciones = Télam 7. Economía = Página 12 8. Clima y geografía = Télam

A continuación, seleccionar las noticias vinculadas a algún tópico relevante (por ejemplo, “Elecciones”) y construir un clasificador para predecir la orientación del diario. Utilizar alguno de los modelos de clasificación vistos a lo largo de al Diplomatura (regresión logística, random forest, etc.). Utilizar como features el “Spanish Billion Word Corpus and Embeddings”, analizado en clase (pueden descargar el embedding en formato .bin del link). ¿Qué resultados arroja el modelo? ¿Es posible mediante el texto de las noticias conocer la línea editorial del diario? Generar las visualizaciones y tablas correspondientes para una correcta evaluación del modelo.

Buscamos la representación vectorial de palabras en español:

load_embeddings <- function(path=NULL, type=c("w2v", "ft")){
        if (type=="w2v"){
                embedding <- word2vec::read.wordvectors(path, 
                                                        type = "bin", 
                                                        normalize = TRUE) %>%
                        as_tibble(rownames="word")
        }
        else if (type=="ft"){
                model <- fastTextR::ft_load(path)
                words <- fastTextR::ft_words(model)
                embedding <- fastTextR::ft_word_vectors(model,
                                                        words) %>%
                        as_tibble(rownames="word")
        }
        
        return(embedding)
}


embedding <- load_embeddings(path = "SBW-vectors-300-min5.bin",
                             type = "w2v")
Warning: The `x` argument of `as_tibble.matrix()` must have unique column names if `.name_repair` is omitted as of tibble 2.0.0.
Using compatibility `.name_repair`.

Nos quedamos con las noticias cuyo mayor gamma está asociado al tópico 1:

docs_topico_1 <- doc_topics_lda %>%
              mutate(gamma = round(gamma, 5)) %>% 
              rename(id = document) %>%
              mutate(id = as.integer(id)) %>% 
              left_join(corpus_base %>% select(id, medio, orientacion) %>% unique()) %>%
              arrange(id, desc(gamma)) %>% 
              group_by(id) %>%
              slice_max(gamma, n = 1) %>% 
              filter(topic == 1&orientacion != 'neutro') %>% 
              mutate(orientacion = case_when(orientacion == '+ conservador'~'conservador',
                                             orientacion == '+ progresista'~'progresista')) 
Joining with `by = join_by(id)`

Buscamos los tokens de las noticias seleccionadas:

words_topico_1 <- docs_topico_1 %>% 
                  left_join(corpus_tidy_v2 %>% select(id, word))
Joining with `by = join_by(id)`

Unimos los tokens del tópico 1 con su embedding:

embeds_topico_1 <- words_topico_1 %>% 
                    left_join(embedding)
Joining with `by = join_by(word)`
docs_embed <- embeds_topico_1 %>%
        group_by(id, orientacion) %>%
        summarise(across(V1:V300, ~mean(.x, na.rm=TRUE))) %>%
        ungroup()
`summarise()` has grouped output by 'id'. You can override using the `.groups` argument.
set.seed(649)
docs_split <- initial_split(docs_embed, strata = orientacion)

train_embed <- training(docs_split)
test_embed <- testing(docs_split)

lasso_spec <- logistic_reg(
        penalty = tune(),
        mixture = 1) %>%
        set_mode("classification") %>%
        set_engine("glmnet") 

docs_embed_recipe <- recipe(orientacion ~ ., data = train_embed) %>%
                     update_role("id", new_role = "ID")


wf_embed <- workflow() %>% 
        add_recipe(docs_embed_recipe) %>%
        add_model(lasso_spec)

grid_lasso <- grid_regular(penalty(), levels = 30)


set.seed(123)
embed_folds <- vfold_cv(train_embed, v = 5)

Entrenamos el modelo:

tune_lasso_embed <- tune_grid(
        wf_embed,
        embed_folds,
        grid = grid_lasso,
        control = control_resamples(save_pred = TRUE)
)

Elegimos los dos mejores modelos según accuracy y error estándar:

show_best(tune_lasso_embed, "accuracy", n=2)
chosen_auc_embed <- tune_lasso_embed %>%
  select_by_one_std_err(metric = "accuracy", -penalty)

chosen_auc_embed
final_params_lasso_embed <- finalize_workflow(wf_embed, chosen_auc_embed)
final_params_lasso_embed
══ Workflow ════════════════════════════════════════════════════════════════════════════════════════════════════════
Preprocessor: Recipe
Model: logistic_reg()

── Preprocessor ────────────────────────────────────────────────────────────────────────────────────────────────────
0 Recipe Steps

── Model ───────────────────────────────────────────────────────────────────────────────────────────────────────────
Logistic Regression Model Specification (classification)

Main Arguments:
  penalty = 0.00853167852417281
  mixture = 1

Computational engine: glmnet 

Fiteamos el modelo elegido con el set de train:

fitted_lasso_embed <- fit(final_params_lasso_embed, train_embed)

Predecimos sobre el set de test:

  preds_embed <- test_embed %>%
          select(orientacion) %>%
          bind_cols(predict(fitted_lasso_embed, test_embed, type="prob")) %>%
          bind_cols(predict(fitted_lasso_embed, test_embed, type="class")) %>% 
  mutate(orientacion = factor(orientacion,levels = c("conservador","progresista")),
         .pred_class = factor(.pred_class,levels = c("conservador","progresista"))) 
       
cm
             Truth
Prediction    conservador progresista
  conservador         143          63
  progresista          22          40

Acá intercambiamos el nivel de las clases para identificar el recall de la clase progresista

  preds_embed_2 <- test_embed %>%
          select(orientacion) %>%
          bind_cols(predict(fitted_lasso_embed, test_embed, type="prob")) %>%
          bind_cols(predict(fitted_lasso_embed, test_embed, type="class")) %>% 
  mutate(orientacion = factor(orientacion,levels = c("progresista","conservador")),
         .pred_class = factor(.pred_class,levels = c("progresista","conservador"))) 

class_metrics(preds_embed_2, truth = orientacion, estimate = .pred_class) 
NA

68% de los casos son clasificados correctamente como conservadores o progresistas. En los casos de medios conservadores, en un 86% de las veces el contenido de las noticias sirve para identificarlos como tales. En contraste solo el 38% de los documentos de medios con orientación progresista pueden ser identificados como tales a partir de su contenido.

LS0tCnRpdGxlOiAiVHJhYmFqbyBGaW5hbCBEaXBsb21hQ1NDIC0gT3BjacOzbiAyIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKZWRpdG9yX29wdGlvbnM6IAogIG1hcmtkb3duOiAKICAgIHdyYXA6IDcyCi0tLQoKIyMgQ2FyZ2EgZGUgbGlicmVyw61hcyBhIHV0aWxpemFyCgpgYGB7ciBsaWJyYXJ5fQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeSh0aWR5dGV4dCkKbGlicmFyeSh0bSkKbGlicmFyeSh0ZXh0c3RlbSkKbGlicmFyeShwYXRjaHdvcmspCmxpYnJhcnkod29yZGNsb3VkKSAKbGlicmFyeShwbG90bHkpCmxpYnJhcnkodG9waWNtb2RlbHMpCmxpYnJhcnkodGljdG9jKQpsaWJyYXJ5KGNvd3Bsb3QpCmxpYnJhcnkocmVzaGFwZTIpCmxpYnJhcnkoc3RtKQpsaWJyYXJ5KHRleHRyZWNpcGVzKQpsaWJyYXJ5KHRleHRjbGVhbikKbGlicmFyeSh0aWR5bW9kZWxzKQpgYGAKCiMjIEV0YXBhIDE6IGNhcmdhIGRlIGRhdG9zIHkgcHJlcHJvY2VzYW1pZW50bwoKRWwgYXJjaGl2byBxdWUgc2UgYWRqdW50YSBjb25zaXN0ZSBlbiB1biBjb3JwdXMgZGUgdW5hcyA3LjAwMCBub3RpY2lhcyBzY3JhcGVhZGFzIGVudHJlIGp1bGlvIHkgc2VwdGllbWJyZSBkZSAyMDE5IGRlIGxvcyBzaWd1aWVudGVzIG1lZGlvcyBkZSBjaXJjdWxhY2nDs24gbmFjaW9uYWw6CgpUw6lsYW0gTGEgTmFjacOzbiBDbGFyw61uIFBlcmZpbCBJbmZvYmFlIE1pbnV0b1VubyBQw6FnaW5hIDEyCgpDb25zdGl0dXllIHVuYSBtdWVzdHJhIGFsZWF0b3JpYSBkZWwgY29ycHVzIGNvbnN0cnVpZG8gcG9yIEZsb3JlbmNpYSBQacOxZXlyw7phIHBhcmEgc3UgdGVzaW5hIGRlIGdyYWRvIOKAnFByb2Nlc2FtaWVudG8gZGVsIGxlbmd1YWplIG5hdHVyYWwgYXBsaWNhZG8gYWwgZXN0dWRpbyBkZSB0w7NwaWNvcyBkZSBub3RpY2lhcyBkZSBzZWd1cmlkYWQgZW4gQXJnZW50aW5hOiBqdWxpbyBhIHNlcHRpZW1icmUgMjAxOeKAnS4gVW5hIGV4cG9zaWNpw7NuIG3DoXMgY29uY2VudHJhZGEgZGUgc3VzIHJlc3VsdGFkb3MgcHVlZGUgZW5jb250cmFyc2UgZW4gZWwgc2lndWllbnRlIGFydMOtY3Vsby4KCkVsIGNvcnB1cyBjb250aWVuZSBsYXMgc2lndWllbnRlcyB2YXJpYWJsZXM6CgppZCA6IGlkZW50aWZpY2Fkb3IgZGUgY2FkYSBkb2N1bWVudG8KdXJsIDogbGluayBhIGxhIG5vdGljaWEgb3JpZ2luYWwKZmVjaGEgOiBmZWNoYSBkZSBwdWJsaWNhY2nDs24KYW5pbyA6IGHDsW8gZGUgcHVibGljYWNpw7NuCm1lcyA6IG1lcyBkZSBwdWJsaWNhY2nDs24KZGlhIDogZGlhIGRlIHB1YmxpY2FjacOzbgptZWRpbyA6IG1lZGlvIGVuIGVsIHF1ZSBmdWUgcHVibGljYWRvCm9yaWVudGFjaW9uOiBjbGFzaWZpY2FjacOzbiAtcHJvdmlzb3JpYS0gZGUgbG9zIG1lZGlvcyBzZWfDum4gc3UgbMOtbmVhCmVkaXRvcmlhbCBwcmVkb21pbmFudGUgKG3DoXMgY29uc2VydmFkb3IsIG3DoXMgcHJvZ3Jlc2lzdGEsIG5ldXRyYWwpCnRpdHVsbwp0ZXh0bwoKIyMjIENhcmdhIGRhdGFzZXQKCmBgYHtyIHJlYWRjc3Z9CmNvcnB1c19iYXNlIDwtIHJlYWRfY3N2KCJNNV9jb3JwdXNfbWVkaW9zLmNzdiIpIApgYGAKCmBgYHtyIGFuYWxpc2lzX2Jhc2V9CiMgRW4gZXN0ZSBhcGFydGFkbyB2ZW1vcyBjdcOhbnRhcyBub3RpY2lhcyBhcG9ydGEgY2FkYSBtZWRpbyBhbCBjb3JwdXMgeSBjYWxjdWxhbW9zIGxhIHByb3BvcmNpw7NuIHNvYnJlIGVsIHRvdGFsLgoKICAgIGNvcnB1c19iYXNlICU+JQogICAgICAgIGdyb3VwX2J5KG1lZGlvKSAlPiUKICAgICAgICBzdW1tYXJpc2Uobj1uKCkpICU+JQogICAgICAgIG11dGF0ZSgKICAgICAgICAgICAgICAgIHRvdGFsID0gc3VtKG4pLAogICAgICAgICAgICAgICAgcHJvcCA9IG4vdG90YWwqMTAwCiAgICAgICAgICAgICAgICApICU+JQogICAgICAgIHVuZ3JvdXAoKSAlPiUKICAgICAgICBzZWxlY3QobWVkaW8sIG4sIHByb3ApICU+JSAKICAgICAgICBhcnJhbmdlKGRlc2MobikpCmBgYAoKIyMjIE5vcm1hbGl6YWNpw7NuIHkgdG9rZW5pemFjacOzbgoKYGBge3IgdG9rZW5zfQpjb3JwdXNfdGlkeSA8LSBjb3JwdXNfYmFzZSAlPiUgCiAgICBtdXRhdGUodGV4dG89IHN0cmluZ2k6OnN0cmlfdHJhbnNfZ2VuZXJhbCh0ZXh0bywgIkxhdGluLUFTQ0lJIiksCiAgICAgICAgIHRpdHVsbyA9IHN0cmluZ2k6OnN0cmlfdHJhbnNfZ2VuZXJhbCh0aXR1bG8sICJMYXRpbi1BU0NJSSIpKSAlPiUgCiAgICBtdXRhdGUodGV4dG8gPSBzdHJfcmVwbGFjZV9hbGwodGV4dG8sICdbWzpkaWdpdDpdXSsnLCAnJykpICU+JSAKICAgIHVubmVzdF90b2tlbnMod29yZCwgdGV4dG8sIHRvX2xvd2VyID0gVFJVRSkKCiMgRW4gZXN0ZSBlc3RlIGPDs2RpZ28gdG9tYW1vcyBlbCBjb3JwdXMgZGUgdGV4dG8gImNvcnB1c19iYXNlIiwgbG8gbm9ybWFsaXphbW9zIGNvbnZpcnRpZW5kbyBlbCB0ZXh0byB5IGVsIHTDrXR1bG8gYSBjYXJhY3RlcmVzIEFTQ0lJLCBlbGltaW5hbW9zIGxvcyBkw61naXRvcyBkZWwgdGV4dG8geSBmaW5hbG1lbnRlIGxvcyBkaXZpZGltb3MgZW4gdG9rZW5zIHBhcmEgc3UgcG9zdGVyaW9yIGFuw6FsaXNpcy4KYGBgCgojIyMgRWxpbWluYXIgc3RvcHdvcmRzCgpgYGB7ciBzdG9wd29yZHN9CnN0b3Bfd29yZHNfMSA8LSByZWFkX2NzdignaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL0FsaXIzejQvc3RvcC13b3Jkcy9tYXN0ZXIvc3BhbmlzaC50eHQnLCBjb2xfbmFtZXM9RkFMU0UpICU+JQogICAgICAgIHJlbmFtZSh3b3JkID0gWDEpICU+JQogICAgICAgIG11dGF0ZSh3b3JkID0gc3RyaW5naTo6c3RyaV90cmFuc19nZW5lcmFsKHdvcmQsICJMYXRpbi1BU0NJSSIpKQoKIyBFbiBlc3RlIGPDs2RpZ28gbGVlbW9zIHVuIGFyY2hpdm8gQ1NWIGRlIHN0b3Agd29yZHMgZW4gZXNwYcOxb2wgdWJpY2FkbyBlbiBsYSBVUkwgZXNwZWNpZmljYWRhLCByZW5vbWJyYW1vcyBsYSBjb2x1bW5hIGEgIndvcmQiIHkgbHVlZ28gdHJhbnNmb3JtYW1vcyBsYXMgcGFsYWJyYXMgZW4gbGEgY29sdW1uYSBwYXJhIGFzZWd1cmFybm9zIGRlIHF1ZSBlc3TDqW4gZW4gZm9ybWF0byBBU0NJSS4gRWwgcmVzdWx0YWRvIGZpbmFsIGVzIHVuIGRhdGFmcmFtZSBsbGFtYWRvICJzdG9wX3dvcmRzXzEiIHF1ZSBjb250aWVuZSBsYSBsaXN0YSBkZSBzdG9wd29yZHMgbm9ybWFsaXphZGFzLgoKc3RvcF93b3Jkc18yIDwtIHJlYWRfY3N2KCJ6X3N0b3B3b3Jkcy50eHQiLCBjb2xfbmFtZXMgPSBGQUxTRSkgJT4lIAogIHJlbmFtZSh3b3JkID0gWDEpICU+JSAKICAgbXV0YXRlKHdvcmQ9c3RyaW5naTo6c3RyaV90cmFuc19nZW5lcmFsKHdvcmQsICJMYXRpbi1BU0NJSSIpKQoKIyDDjWRlbSBwZXJvIGVsIGFyY2hpdm8gQ1NWIGxsYW1hZG8gInpfc3RvcHdvcmRzLnR4dCIgc2UgY2FyZ2EgZGVzZGUgbGEgY2FycGV0YS4KCnN0b3Bfd29yZHNfZnVsbCA8LSBzdG9wX3dvcmRzXzEgJT4lIAogIGJpbmRfcm93cyhzdG9wX3dvcmRzXzIpICU+JSAKICBkaXN0aW5jdCgpCgojIEVuIGVzdGUgcGFzbyBjb21iaW5hbW9zIGxvcyBkb3MgZGF0YWZyYW1lcyBkZSBzdG9wd29yZHMsIGVsaW1pbmFtb3MgbGFzIGZpbGFzIGR1cGxpY2FkYXMgeSBndWFyZGFtb3MgZWwgcmVzdWx0YWRvIGVuIHVuIG51ZXZvIGRhdGFmcmFtZSBsbGFtYWRvICJzdG9wX3dvcmRzX2Z1bGwiLiBFc3RlIGRhdGFmcmFtZSBjb250aWVuZSBsYSBsaXN0YSBjb21wbGV0YSB5IMO6bmljYSBkZSBzdG9wd29yZHMgbm9ybWFsaXphZGFzLgoKY29ycHVzX3RpZHkgPC0gY29ycHVzX3RpZHkgJT4lIAogIGFudGlfam9pbihzdG9wX3dvcmRzX2Z1bGwpCgojIEVuIGVzdGUgY8OzZGlnbyBlbGltaW5hbW9zIGxhcyBzdG9wd29yZHMgZGVsIGNvcnB1cyBkZSB0ZXh0byAiY29ycHVzX3RpZHkiIHV0aWxpemFuZG8gbGEgbGlzdGEgZGUgc3RvcHdvcmRzIGNvbnRlbmlkYSBlbiBlbCBkYXRhZnJhbWUgInN0b3Bfd29yZHNfZnVsbCIuIEVzdGUgcGFzbyBlbiBlbCBwcm9jZXNhbWllbnRvIGRlIHRleHRvIGVzIG5lY2VzYXJpbyBwYXJhIGVsaW1pbmFyIHBhbGFicmFzIHF1ZSBubyBhcG9ydGFuIHNpZ25pZmljYWRvIHBhcmEgZWwgYW7DoWxpc2lzIHBvc3Rlcmlvci4KYGBgCgojIyMgQ29ycmVjY2nDs24gImHDsW9zIgoKYGBge3IgY29ycmVjY2lvbn0KY29ycHVzX3RpZHkgPC0gY29ycHVzX3RpZHkgJT4lIAogIG11dGF0ZSh3b3JkID0gY2FzZV93aGVuKAogICAgd29yZCA9PSAnYW5vJyB+ICdhbmlvJywKICAgIHdvcmQgPT0gJ2Fub3MnIH4gJ2FuaW8nLAogICAgVFJVRSB+IHdvcmQKICApKQpgYGAKCiMjIEV0YXBhIDI6IENvbnNpZ25hcwoKwr9DdcOhbGVzIHNvbiBsYXMgcGFsYWJyYXMgbcOhcyB1dGlsaXphZGFzIGVuIGNhZGEgdW5vIGRlIGxvcyBtZWRpb3M/IMK/UHVlZGVuIHZlcnNlIGRpZmVyZW5jaWFzPyAoVGVuZXIgZW4gY3VlbnRhIGxhcyBkaWZlcmVudGVzIG3DqXRyaWNhcyB0cmFiYWphZGFzIGVuIGVsIGN1cnNvOiB0ZiwgdGYtaWRmLCBldGMuKSBHZW5lcmFyIGxhcyB2aXN1YWxpemFjaW9uZXMgcXVlIGNvbnNpZGVyZSBtw6FzIHBlcnRpbmVudGVzIHBhcmEgcmVzcG9uZGVyIGxhIHByZWd1bnRhLgoKIyMjIEV4cGxvcmFjacOzbiBwYWxhYnJhcyBtw6FzIGZyZWN1ZW50ZXMKCkNvbWVuemFtb3MgZXhwbG9yYW5kbyBlbCBjb3JwdXMgZW4gZm9ybWF0byB0aWR5IHBhcmEgaWRlbnRpZmljYXIgcsOhcGlkYW1lbnRlIGFsZ3Vub3MgZGUgbG9zIHTDqXJtaW5vcyBtw6FzIGZyZWN1ZW50ZXMgcG9yIG1lZGlvLgoKYGBge3IgZXhwbG9yZV9mcmVxc30KY29ycHVzX3RpZHkgJT4lCiAgICAgICAgZ3JvdXBfYnkod29yZCwgbWVkaW8pICU+JQogICAgICAgIHN1bW1hcmlzZShuPW4oKSkgJT4lCiAgICAgICAgYXJyYW5nZShkZXNjKG4pKQoKIyBNZWRpYW50ZSBlc3RlIGPDs2RpZ28gYWdydXBhbW9zIGxhcyBwYWxhYnJhcyDDum5pY2FzIGVuIGVsIGNvcnB1cywgY29udGFtb3MgZWwgbsO6bWVybyBkZSBvY3VycmVuY2lhcyBkZSBjYWRhIHBhbGFicmEgeSBsYXMgb3JkZW5hbW9zIGVuIGZ1bmNpw7NuIGRlIHN1IGZyZWN1ZW5jaWEgZGUgYXBhcmljacOzbiwgbW9zdHJhbmRvIHByaW1lcm8gbGFzIHBhbGFicmFzIG3DoXMgZnJlY3VlbnRlcy4KCmNvcnB1c190aWR5ICU+JQogIGZpbHRlcihtZWRpbyA9PSAnaW5mb2JhZScpICAlPiUgCiAgZ3JvdXBfYnkod29yZCkgJT4lCiAgc3VtbWFyaXNlKG49bigpKSAlPiUKICBhcnJhbmdlKGRlc2MobikpCiAgICAgICAgCiMgTWlyYWRhIHLDoXBpZGEgYSBsYXMgcGFsYWJyYXMgbcOhcyBmcmVjdWVudGVzIGRlIGFsZ3Vub3MgbWVkaW9zCgpjb3JwdXNfdGlkeSAlPiUKICAgICAgICBncm91cF9ieShtZWRpbywgd29yZCkgJT4lCiAgICAgICAgc3VtbWFyaXNlKG49bigpKSAlPiUKICAgICAgICBhcnJhbmdlKGRlc2MobikpICU+JQogICAgICAgIHBpdm90X3dpZGVyKG5hbWVzX2Zyb20gPSBtZWRpbywKICAgICAgICAgICAgICAgICAgICB2YWx1ZXNfZnJvbSA9IG4pCgojIEVzdGUgY8OzZGlnbyBjYWxjdWxhIGVsIHJlY3VlbnRvIGRlIG9jdXJyZW5jaWFzIGRlIGNhZGEgcGFsYWJyYSBlbiBlbCBjb3JwdXMgZGUgdGV4dG8gYWdydXBhZG8gc2Vnw7puIGVsIG1lZGlvIGRlIGNvbXVuaWNhY2nDs24sIHkgbHVlZ28gcmVvcmdhbml6YSBlc3RvcyByZXN1bHRhZG9zIGVuIHVuIGZvcm1hdG8gbcOhcyBhbmNobyBkb25kZSBjYWRhIG1lZGlvIHRpZW5lIHN1cyByZWN1ZW50b3MgZGUgb2N1cnJlbmNpYXMgYXNvY2lhZG9zIHBhcmEgY2FkYSBwYWxhYnJhLgoKYGBgCgpgYGB7ciB3b3JkX2NvdW50c30Kd29yZF9jb3VudHNfMTAgPC0gY29ycHVzX3RpZHkgJT4lCiAgICAgICAgZ3JvdXBfYnkobWVkaW8sIHdvcmQpICU+JQogICAgICAgIHN1bW1hcmlzZShuPW4oKSkgJT4lCiAgICAgICAgc2xpY2VfbWF4KG4sIG4gPSAxMCkgJT4lIAogICAgICAgIHVuZ3JvdXAoKQoKIyBFbiBlc3RlIHBhc28gY2FsY3VsYW1vcyBlbCByZWN1ZW50byBkZSBvY3VycmVuY2lhcyBkZSBjYWRhIHBhbGFicmEgZW4gZWwgY29ycHVzLCBhZ3J1cGFkYXMgcG9yIGxhIGNvbHVtbmEgIm1lZGlvIiwgeSBhbG1hY2VuYSBlc3RvcyByZWN1ZW50b3MgZW4gdW4gbnVldm8gZGF0YWZyYW1lIGxsYW1hZG8gIndvcmRfY291bnRzIiBwYXJhIHBhc2FybGUgYSBsYSBmdW5jacOzbiBkZSBncmFmaWNhZG8uCgp3b3JkX2NvdW50c19hbGxfMTAgPC0gY29ycHVzX3RpZHkgJT4lCiAgICAgICAgZ3JvdXBfYnkod29yZCkgJT4lCiAgICAgICAgc3VtbWFyaXNlKG49bigpKSAlPiUKICAgICAgICBzbGljZV9tYXgobiwgbiA9IDEwKSAlPiUgCiAgICAgICAgdW5ncm91cCgpICU+JSAKICAgICAgICBtdXRhdGUobWVkaW8gPSAnZ2VuZXJhbCcpCgojIEFxdcOtIGNhbGN1bGFtb3MgZWwgcmVjdWVudG8gZGUgb2N1cnJlbmNpYXMgZGUgY2FkYSBwYWxhYnJhIGVuIGVsIGNvcnB1cywgc2luIGFncnVwYXIgcG9yIG1lZGlvLiBMdWVnbywgc2VsZWNjaW9uYW1vcyBsYXMgMTAgcGFsYWJyYXMgbcOhcyBjb211bmVzIGVuIHRvZG8gZWwgY29ycHVzLiBBIGVzdG9zIHJlc3VsdGFkb3MgbGVzIGFncmVnYW1vcyBsYSBldGlxdWV0YSAiZ2VuZXJhbCIgZW4gbGEgY29sdW1uYSAibWVkaW8iIHBhcmEgcG9kZXIgY29tcGFyYXIgdmlzdWFsbWVudGUgbGEgZGlzdHJpYnVjacOzbiBnZW5lcmFsIGNvbiBsYSBkaXN0cmlidWNpw7NuIHBvciBtZWRpbwoKd29yZF9jb3VudHNfdG9wXzEwIDwtIHdvcmRfY291bnRzXzEwICU+JSAKICBiaW5kX3Jvd3Mod29yZF9jb3VudHNfYWxsXzEwKQoKIyBVbmlmaWNvIGVuIHVuIGRhdGFzZXQKYGBgCgpBIGNvbnRpbnVhY2nDs24gY3JlYW1vcyB1bmEgZnVuY2nDs24gcGFyYSBhdXRvbWF0aXphciB0b2RhcyBsYXMgdmlzdWFsaXphY2lvbmVzIHJlbGFjaW9uYWRhcyBhIGxhcyBtw6l0cmljYXMgZGVsIGNvcnB1cy4gCgpgYGB7ciBjcmVhcl92aXp9CmNyZWFyX2dyYWZfd29yZHMgPC0gZnVuY3Rpb24oZGF0YSkgewogIG1lZGlvcyA8LSB1bmlxdWUoZGF0YSRtZWRpbykKICBncmFmaWNvcyA8LSBsaXN0KCkKICAKICBmb3IgKG1lZGlvX2FjdHVhbCBpbiBtZWRpb3MpIHsKICAgIGRhdG9zX21lZGlvIDwtIGRhdGEgJT4lCiAgICAgIGZpbHRlcihtZWRpbyA9PSBtZWRpb19hY3R1YWwpICU+JQogICAgICBtdXRhdGUod29yZCA9IGZjdF9yZW9yZGVyKHdvcmQsIG4pKQogICAgCiAgICBncmFmaWNvIDwtIGdncGxvdChkYXRvc19tZWRpbywgYWVzKG4sIHdvcmQpKSArCiAgICAgICAgICAgICAgICBnZW9tX2NvbCgpICsKICAgICAgICAgICAgICAgIGdlb21fdGV4dChhZXMobGFiZWwgPSBuKSwgcG9zaXRpb24gPSBwb3NpdGlvbl9zdGFjayh2anVzdCA9IDAuNSksIGZhbWlseSA9ICJDb3VyaWVyIiwgY29sb3IgPSAid2hpdGUiKSArCiAgICAgICAgICAgICAgICBsYWJzKHRpdGxlID0gbWVkaW9fYWN0dWFsLAogICAgICAgICAgICAgICAgICAgICB4ID0gIkZyZWN1ZW5jaWEiLAogICAgICAgICAgICAgICAgICAgICB5ID0gIlBhbGFicmEiKSArCiAgICAgICAgICAgICAgICB0aGVtZV9jbGFzc2ljKCkrCiAgICAgICAgICAgICAgICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0PSAwLjUpLAogICAgICAgICAgICAgICAgICAgICAgYXhpcy50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgICAgICAgICAgIGF4aXMudGlja3MueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgICAgICAgICAgIHRleHQgPSBlbGVtZW50X3RleHQoZmFtaWx5ID0gIkNvdXJpZXIiKSkKICAgIAogICAgZ3JhZmljb3NbW21lZGlvX2FjdHVhbF1dIDwtIGdyYWZpY28KICB9CiAgCiAgd3JhcF9wbG90cyhncmFmaWNvcykKfQoKIyBFc3RhIGZ1bmNpw7NuIGNyZWEgcGFyYSBjYWRhIG1lZGlvIHVuIGdyw6FmaWNvIGNvbiBlbCB0b3AgMTAgZGUgcGFsYWJyYXMgc2Vnw7puIGNhZGEgbcOpdHJpY2EgYW5hbGl6YWRhCgpgYGAKCkVsIGVqZSB4IG11ZXN0cmEgbGFzIHBhbGFicmFzIHkgZWwgZWplIHkgbXVlc3RyYSBsYSBmcmVjdWVuY2lhIGRlIGNhZGEgcGFsYWJyYS4gRWwgZ3LDoWZpY28gZXN0w6Egc2VnbWVudGFkbyBlbiBwYW5lbGVzIHBvciBjYWRhIG1lZGlvLCB5IGxhcyBlc2NhbGFzIGRlbCBlamUgeSBzZSBhanVzdGFuIHBhcmEgY2FkYSBwYW5lbCBpbmRpdmlkdWFsLgoKYGBge3Igdml6fQpjcmVhcl9ncmFmX3dvcmRzKHdvcmRfY291bnRzX3RvcF8xMCkKCiMgRXN0ZSBjw7NkaWdvIGdlbmVyYSB1bmEgdmlzdWFsaXphY2nDs24gZGUgbGFzIHBhbGFicmFzIG3DoXMgY29tdW5lcyBlbiBlbCBjb3JwdXMsIGRlc2dsb3NhZGFzIHBvciBtZWRpbyB5IGVuIGdlbmVyYWwuIExhcyBwYWxhYnJhcyBtw6FzIGNvbXVuZXMgc2UgbXVlc3RyYW4gZW4gb3JkZW4gZGVzY2VuZGVudGUgZGUgZnJlY3VlbmNpYSBlbiBjYWRhIGNhdGVnb3LDrWEuCmBgYAoKQWxndW5hcyBvYnNlcnZhY2lvbmVzIHByZWxpbWluYXJlczoKCi0gICBDbGFyw61uIGUgSW5mb2JhZSBhcG9ydGFuIGNhZGEgdW5vIGVsIDIyJSBkZSBsYXMgbm90aWNpYXMgYWwgY29ycHVzLiBFbCBwZXNvIGRlIGxhcyBwYWxhYnJhcyBtw6FzIGltcG9ydGFudGVzIHBhcmEgZXN0b3MgbWVkaW9zIGVuIGVsIHJhbmtpbmcgZ2VuZXJhbCBlcyBhbHRvLgoKLSAgIEHDsW8gcGFyZWNlIHVuYSBzdG9wd29yZCBwb3JxdWUgZXMgdW5hIHBhbGFicmEgcXVlIHByZXNlbnRhIHVuYSBmcmVjdWVuY2lhIGFsdGEgaW50cmEgZSBpbnRlciBtZWRpb3MuCgotICAgQ3LDs25pY2EsIExOIGUgSW5mb2JhZSBzb24gbG9zIMO6bmljb3MgcXVlIG5vIGluY2x1eWVuIG5vbWJyZXMgcHJvcGlvcwplbnRyZSBsYXMgcGFsYWJyYXMgbcOhcyBmcmVjdWVudGVzLgoKLSAgIEVuIExOIGhheSBtdWNoYXMgcmVmZXJlbmNpYXMgYSByZWRlcyBzb2NpYWxlcyB5IGFjY2lvbmVzIGVuIGVsbGFzIChndWFyZGFyLCBjb21wYXJ0aXIpLiBMYXMgYWNjaW9uZXMgeSAiZnVlbnRlIiBwb2Ryw61hbiBzZXIgc3RvcHdvcmRzLiBJbmNsdXNvIGxhcyBtZW5jaW9uZXMgYSBSUlNTIHBvZHLDrWFuIHNlciBwcm9kdWN0byBkZWwgc2NyYXBwaW5nIHkgbm8gZGVsIGNvbnRlbmlkbyBkZSBsYXMgbm90YXMuCgotICAgTWFjcmkgYXBhcmVjZSBjb21vIGVsIHBvbMOtdGljbyBtw6FzIG5vbWJyYWRvLiBMw7NnaWNhbWVudGUsIGVzIHVuIHJlc3VsdGFkbyBlc3BlcmFibGUgc2kgc2UgdGllbmUgZW4gY3VlbnRhIHF1ZSBsYXMgbm90YXMgcmVsZXZhZGFzIGNvcnJlc3BvbmRlbiBhbCDDumx0aW1vIGHDsW8gZGUgc3UgZ29iaWVybm8uIAoKLSAgSW5mb2JhZSB5IHNvYnJlIHRvZG8gQ3LDs25pY2EgcGFyZWNlbiBjdWJyaXIgdGVtYXMgbcOhcyBnZW5lcmFsZXMgcXVlIGxvcyBkZW3DoXMgbWVkaW9zLCBhbCBpbmNsdWlyIHBhbGFicmFzIGNvbW8gInBvbMOtY8OtYSIsICJjaXVkYWQiLCAibXVqZXIiLCAiaG9tYnJlIiwgImNhc2EiLCAicGVyc29uYXMiLCAibXVuZG8iLCAidmlkYSIuIEluY2x1c28sIGxhIGZyZWN1ZW5jaWEgZGUgbGEgcGFsYWJyYSAicG9saWPDrWEiIGVuIENyw7NuaWNhICgjMiBvICMxIHNpIHNlIGV4Y2x1eWUgZWwgdMOpcm1pbm8gImFuaW8iKSBwb2Ryw61hIGluZGljYXIgcXVlIGxhIG1heW9yIHBhcnRlIGRlIGxhIGNvYmVydHVyYSBkZSBlc3RlIG1lZGlvIHNlIG9yaWVudGEgYSBsYSB0ZW3DoXRpY2EgcG9saWNpYWwuIERlIHRvZGFzIGZvcm1hcywgZW4gZWwgdG9wIDEwIHRhbWJpw6luIGFwYXJlY2VuIHBhbGFicmFzIGNvbW8gImdvYmllcm5vIiB5ICJwcmVzaWRlbnRlIi4KCi0gICBMYSBwYWxhYnJhICJmcmVudGUiIGFwYXJlY2UgY29uIGZyZWN1ZW5jaWEgZW4gdmFyaW9zIG1lZGlvcywgbG8gcXVlIG5vcyBpbmNsaW5hIGEgY29ycm9ib3JhciBzaSByZWZpZXJlIGEgZnJlbnRlcyBlbGVjdG9yYWxlcyBvIHNpbXBsZW1lbnRlIGEgdW4gYWR2ZXJiaW8gZGUgbHVnYXIgeSwgZW4gZXN0ZSBjYXNvLCBkZWJlcsOtYSBjb25zaWRlcmFyc2UgY29tbyBzdG9wd29yZC4KCi0gICBBIG1vZG8gZGUgc8OtbnRlc2lzLCBwb2Ryw61hIGFkdmVydGlyc2UgcXVlIGxhcyBwYWxhYnJhcyBjb24gbWF5b3IgZnJlY3VlbmNpYSBkZSBhcGFyaWNpw7NuIGVuIGxhIHRvdGFsaWRhZCBkZSBsb3MgbWVkaW9zIHNlIGNvcnJlc3BvbmRlbiBjb24gbGEgdGVtw6F0aWNhIHBvbMOtdGljYS4gRXMgcHJvYmFibGUgcXVlIGVzdGEgdmluY3VsYWNpw7NuIHNlYSB1biBkZXNwcmVuZGltaWVudG8gZGVsIGVzY2VuYXJpbyBlbGVjdG9yYWwgcXVlIHNpZ27DsyBlbCBwZXLDrW9kbyBiYWpvIGFuw6FsaXNpcy4KCiMjIyBUZXJtIEZyZXF1ZW5jeSAtIFRGOgoKQSBtb2RvIGRlIHByw6FjdGljYSBnZW5lcmFtb3MgZWwgY8OhbGN1bG8gZGUgdGVybSBmcmVxdWVuY3kgbWFudWFsbWVudGUgcGFyYSB2aXN1YWxpemFyIGxhIGRpc3RyaWJ1Y2nDs24gZGUgdMOpcm1pbm9zIHNlZ8O6biBzdSBmcmVjdWVuY2lhIHJlc3BlY3RvIGFsIHRvdGFsIGRlIHTDqXJtaW5vcywgYXBlcnR1cmFkbyBwb3IgbWVkaW8uCgpgYGB7ciB3b3Jkc190aWR5fQojIExhICJ0ZXJtIGZyZXF1ZW5jeSIgKGZyZWN1ZW5jaWEgZGUgdMOpcm1pbm8pIGVzIHVuYSBtw6l0cmljYSBwYXJhIGV2YWx1YXIgbGEgaW1wb3J0YW5jaWEgZGUgdW5hIHBhbGFicmEgZW4gdW4gZG9jdW1lbnRvIG8gY29ycHVzIGRlIGRvY3VtZW50b3MsIGEgcGFydGlyIGRlIGxhIG1lZGljacOzbiBkZSBsYSBmcmVjdWVuY2lhIGRlIHN1IGFwYXJpY2nDs24gZW4gY29tcGFyYWNpw7NuIGNvbiBlbCBuw7ptZXJvIHRvdGFsIGRlIHBhbGFicmFzLgoKd29yZHNfdGlkeSA8LSBjb3JwdXNfdGlkeSAlPiUgCiAgZ3JvdXBfYnkobWVkaW8sIHdvcmQpICU+JSAjIGVuIGZ1bmNpw7NuIGRlIGxhIGNvbnNpZ25hLCBoYWNlbW9zIGVsIGNvbnRlbyBkZSB0w6lybWlub3MgcG9yIG1lZGlvCiAgc3VtbWFyaXNlKG49bigpKSAlPiUgCiAgYXJyYW5nZShkZXNjKG4pKSAKCnRvdGFsX3dvcmRzIDwtIHdvcmRzX3RpZHkgJT4lCiAgICAgICAgZ3JvdXBfYnkobWVkaW8pICU+JQogICAgICAgIHN1bW1hcml6ZSh0b3RhbCA9IHN1bShuKSkgCgp3b3Jkc190aWR5IDwtIHdvcmRzX3RpZHkgJT4lIAogIGxlZnRfam9pbih0b3RhbF93b3JkcykgJT4lIAogIHVuZ3JvdXAoKSAlPiUgCiAgYXJyYW5nZShkZXNjKG4pKQoKIyBFbiBlc3RlIGPDs2RpZ28gY29udGFtb3MgbGEgZnJlY3VlbmNpYSBkZSBjYWRhIHBhbGFicmEgZW4gZWwgY29ycHVzIGRlbCBtZWRpbyB5IHRhbWJpw6luIGVsIHRvdGFsIGRlIHBhbGFicmFzIGVuIGVsIG1lZGlvLiBDb24gZXN0b3MgZGF0b3MgcG9kZW1vcyBjYWxjdWxhciBhIGNvbnRpbnVhY2nDs24gbGEgaW1wb3J0YW5jaWEgZGUgY2FkYSB0w6lybWlubyBlbiBlbCBtZWRpby4KYGBgCgpFbiBlc3RhIHZpc3VhbGl6YWNpw7NuIHBvZGVtb3MgaW50ZXJhY3R1YXIgcGFyYSBpZGVudGlmaWNhciBsYSBjYW50aWRhZCBkZSB0w6lybWlub3MgcG9yIHRmOgoKYGBge3IgdGZfdml6X21hbnVhbH0KdGZfdml6IDwtIHdvcmRzX3RpZHkgJT4lIG11dGF0ZSh0ZiA9IG4vdG90YWwpICU+JQogICAgICAgICAgZ2dwbG90KGFlcyh0ZikpICsKICAgICAgICAgICAgICAgIGdlb21faGlzdG9ncmFtKHNob3cubGVnZW5kID0gRkFMU0UpICsKICAgICAgICAgICAgICAgIGNvb3JkX2ZsaXAoKSsKICAgICAgICAgICAgICAgIHhsaW0oTkEsIDAuMDAwMikgKwogICAgICAgICAgICAgICAgZmFjZXRfd3JhcCh+bWVkaW8pICsKICAgICAgICAgICAgICAgIHRoZW1lX2NsYXNzaWMoKSsKICAgICAgICAgICAgICAgIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3Q9IDAuNSksCiAgICAgICAgICAgICAgICAgICAgICBheGlzLnRpdGxlID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICAgICAgICAgICAgYXhpcy50aWNrcy54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICAgICAgICAgICAgdGV4dCA9IGVsZW1lbnRfdGV4dChmYW1pbHkgPSAiQ291cmllciIpKQogICAgICAgICAgICAgICAgICAKZ2dwbG90bHkodGZfdml6KQoKYGBgCgpWZW1vcyBxdWUgcGFyYSBsb3MgbWVkaW9zIG3DoXMgbWFzaXZvcyBzZSBjdW1wbGUgbGEgTGV5IGRlIFppcGY6IHBvY2FzIHBhbGFicmFzIG9jdXJyZW4gbXVjaGFzIHZlY2VzLiBMb3MgY29ycHVzIGRlIENyw7NuaWNhLCBNaW51dG8gVW5vIHkgVMOpbGFtIHRpZW5lbiBtZW5vcyB0w6lybWlub3MgcXVlIGxvcyBkZW3DoXMgeSBsYSB0ZiB0aWVuZSB1biByYW5nbyBtZW5vciAodXNvIGRlIHBhbGFicmFzIG1lam9yIGRpc3RyaWJ1aWRvKS4KCiMjIyBUZXJtIEZyZXF1ZW5jeSAtIEludmVyc2UgRG9jdW1lbnQgRnJlcXVlbmN5IChURi1JREYpCgpFbiBlc3RhIGV0YXBhIGNvbXBsZW1lbnRhbW9zIGVsIGFuw6FsaXNpcyBjb24gbGFzIG3DqXRyaWNhcyBkZSBpZGYgeSB0Zl9pZGYsIHBhcmEgb2J0ZW5lciB1bmEgbWVkaWRhIG3DoXMgY29tcGxldGEgZGUgbGEgaW1wb3J0YW5jaWEgZGUgbG9zIHTDqXJtaW5vcyBlbiBlbCBjb3JwdXMgZGUgZG9jdW1lbnRvcy4KCmBgYHtyIHRmX2lkZn0KdGZfaWRmIDwtIHdvcmRzX3RpZHkgJT4lIAogIGJpbmRfdGZfaWRmKHdvcmQsIG1lZGlvLCBuKSAKCnRmX2lkZiAlPiUgc2VsZWN0KC10b3RhbCkgJT4lIAogIGFycmFuZ2UoZGVzYyh0Zl9pZGYpKQoKIyBFbiBlc3RlIGPDs2RpZ28gY2FsY3VsYW1vcyBsYXMgbcOpdHJpY2FzIHRmLCBpZGYgeSB0Zl9pZGYgcGFyYSB0b2RvcyBsb3MgdMOpcm1pbm9zIGRlbCBjb3JwdXMKYGBgCgpDcmVhbW9zIHRyZXMgZGF0YXNldCwgdW5vIHBvciBtw6l0cmljYSwgY29uIGVsIHRvcCB0ZW4gZGUgdMOpcm1pbm9zIHBhcmEgcGFzYXJsZSBhIGxhIGZ1bmNpw7NuIGRlIHZpc3VhbGl6YWNpw7NuLgoKYGBge3IgdG9wX3Rlbl9tZXRyaWNhc30KdGZfMTAgPC0gdGZfaWRmICU+JQogICAgICAgIGdyb3VwX2J5KG1lZGlvKSAlPiUKICAgICAgICBzbGljZV9tYXgodGYsIG4gPSAxMCkgJT4lIAogICAgICAgIHNlbGVjdChtZWRpbywgd29yZCwgdGYpICU+JSAKICAgICAgICByZW5hbWUobiA9IHRmKSAlPiUKICAgICAgICBtdXRhdGUobiA9IGxvZyhuICsgMSkgKiAxMDApICU+JSAKICAgICAgICBtdXRhdGUobiA9IHJvdW5kKG4sIDQpKQoKaWRmXzEwIDwtIHRmX2lkZiAlPiUKICAgICAgICAgIGdyb3VwX2J5KG1lZGlvKSAlPiUKICAgICAgICAgIGFycmFuZ2UoZGVzYyhpZGYpKSAlPiUKICAgICAgICAgIHNsaWNlX2hlYWQobiA9IDEwKSAlPiUKICAgICAgICAgIHNlbGVjdChtZWRpbywgd29yZCwgaWRmKSAlPiUKICAgICAgICAgIHJlbmFtZShuID0gaWRmKSAlPiUKICAgICAgICAgIG11dGF0ZShuID0gbG9nKG4gKyAxKSAqIDEwMCkgJT4lIAogICAgICAgICAgbXV0YXRlKG4gPSByb3VuZChuLCA0KSkKCnRmX2lkZl8xMCA8LSB0Zl9pZGYgJT4lCiAgICAgICAgZ3JvdXBfYnkobWVkaW8pICU+JQogICAgICAgIGFycmFuZ2UoZGVzYyhpZGYpKSAlPiUKICAgICAgICBzbGljZV9oZWFkKG4gPSAxMCkgJT4lIAogICAgICAgIHNlbGVjdChtZWRpbywgd29yZCwgdGZfaWRmKSAlPiUgCiAgICAgICAgcmVuYW1lKG4gPSB0Zl9pZGYpICU+JQogICAgICAgIG11dGF0ZShuID0gbG9nKG4gKyAxKSAqIDEwMCkgJT4lIAogICAgICAgIG11dGF0ZShuID0gcm91bmQobiwgNCkpCmBgYAoKUGFzYW1vcyBjYWRhIHVubyBkZSBsb3MgdG9wIHRlbiBkZSB0w6lybWlub3MgcG9yIGxhIGZ1bmNpw7NuIGRlIGdyYWZpY2FkbyBwYXJhIHZpc3VhbGl6YXIgbG9zIHTDqXJtaW5vcyBtw6FzIGltcG9ydGFudGVzIHNlZ8O6biBjYWRhIG3DqXRyaWNhLgoKYGBge3IgdGZfdml6fQpjcmVhcl9ncmFmX3dvcmRzKHRmXzEwKQpgYGAKCmBgYHtyIGlkZl92aXp9CmNyZWFyX2dyYWZfd29yZHMoaWRmXzEwKQpgYGAKCmBgYHtyIHRmX2lkZl92aXp9CmNyZWFyX2dyYWZfd29yZHModGZfaWRmXzEwKQpgYGAKCkFuw6FsaXNpcyBwcmVsaW1pbmFyIGRlIFRGLUlERjoKCi0gICBFbiBwcmltZXIgbHVnYXIgaWRlbnRpZmljYW1vcyBsYSBuZWNlc2lkYWQgZGUgYWdyZWdhciBwYWxhYnJhcyBhbAogICAgbGlzdGFkbyBkZSBzdG9wd29yZHMuIEVudGVuZGVtb3MgcXVlIG5vIGFwb3J0YW4gaW5mb3JtYWNpw7NuIHNvYnJlIGVsCiAgICBjb250ZW5pZG8gdSBvcmllbnRhY2nDs24gZGUgbGFzIG5vdGljaWFzIHkgYSBzdSB2ZXogc29uIMO6bmljb3MgcGFyYQogICAgYWxndW5vcyBtZWRpb3MgeSBlc28gZ2VuZXJhIHVuIGFsdG8gdGZfaWRmLgoKICAgIEVqZW1wbG9zOiBqcGUsIGFwLCBlbWosIGNyb25pY2EuY29tLmFyLCBmdmF6cXVleiwgY3JvbmljYXZpcmFsZXMsCiAgICBoZCwgcGVtZXgsIGd0LCBhZnYsIGpwZywgbWludXRvdW5vLmNvbSwgYW1iaXRvLmNvbSwgaXZhbm92aWNoLAogICAgbG9hZGluZywgcGFnaW5haSwgcHJvdGVjdGVkLCBseHMsIGVtYWlsLCByLmMsY3AsIGZlbCxsLmwsZC5zLCBlYSwKICAgIGYuZiwgZi5kLnMsIGZoLCBhLmcsIHBjdCwgdGVsYW0ubGEsIG5hY2lvbmFsLmVsCgotICAgUG9kZW1vcyBpZGVudGlmaWNhciAiZmlybWFzIiBkZSBwZXJpb2Rpc3RhcyAobm9tYnJlcyBkZSB1c3VhcmlvIG8KICAgIHNpZ2xhcykgcXVlIHRhbXBvY28gc29uIGluZm9ybWF0aXZhcyBzb2JyZSBlbCBjb250ZW5pZG8gZGVsIGNvcnB1cy4KCiMjIEV0YXBhIDEuMjogQW1wbGlhY2nDs24gc3RvcHdvcmRzIHkgcmVwcm9jZXNhbWllbnRvIGRlIGxhIGJhc2UKCkVsIGFuw6FsaXNpcyByZWFsaXphZG8gbm9zIHBlcm1pdGnDsyBlbmNvbnRyYXIgbnVtZXJvc2FzIHBhbGFicmFzIGluY2x1aWRhcyBlbiBlbCBjb3JwdXMgcXVlIGNyZWVtb3Mgc29uIHByb2R1Y3RvIGRlbCBzY3JhcHBpbmcuIFByZXZpbyBhIGxhIG1vZGVsaXphY2nDs24gcGFyYSBpZGVudGlmaWNhciB0w7NwaWNvcyBjcmVlbW9zIHBlcnRpbmVudGUgYW1wbGlhciBlbCBsaXN0YWRvIGRlIHN0b3B3b3JkcyB5IHJlcGV0aXIgZWwgcHJlcHJvY2VzYW1pZW50byBkZSBsb3MgZGF0b3MuIAoKUGFyYSBhbXBsaWFyIGVsIGxpc3RhZG8gZGUgc3RvcHdvcmRzIHBhcnRpbW9zIGRlbCBsaXN0YWRvIGRlIHBhbGFicmFzIGNvbiBzdXMgbcOpdHJpY2FzIGRlIHRmIGUgaWRmLgoKYGBge3Igc3RvcHdvcmRzX3YyfQojIE1lZGlhbnRlIGVsIHNpZ3VpZW50ZSBjw7NkaWdvIGVsaW1pbmFtb3MgcGFsYWJyYXMgcXVlIGluY2x1eWVuIHB1bnRvcyB5IHBhcmVjZW4gcHJvZHVjdG8gZGUgc2NyYXBwaW5nIHdlYi4gCm51ZXZhc19zdG9wd29yZHNfMSA8LSB0Zl9pZGYgJT4lIAogIHNlbGVjdCh3b3JkKSAlPiUgCiAgZmlsdGVyKGdyZXBsKCdcXC4nLCB3b3JkKSkgJT4lIAogIHB1bGwod29yZCkKCiMgRW4gZXN0ZSBwYXNvIGVsaW1pbmFtb3MgcGFsYWJyYXMgZGUgZG9zIGNhcmFjdGVyZXMsIHNhbHZvIGNvbnRhZGFzIGV4Y2VwY2lvbmVzIHF1ZSB0aWVuZW4gc2VudGlkby4KbnVldmFzX3N0b3B3b3Jkc18yIDwtIHRmX2lkZiAlPiUgCiAgc2VsZWN0KHdvcmQpICU+JSAKICBmaWx0ZXIoc3RyX2xlbmd0aCh3b3JkKSA9PSAyICYgISh3b3JkICVpbiUgYygncGonLCAnZmUnLCAnZHQnLCAndHYnLCAna20nLCAnY3YnLCAnZHInLCAnaXQnLCAnZGonLCAndXgnKSkpICU+JSAKICBwdWxsKHdvcmQpCgojIEVzdGUgY8OzZGlnbyBlbGltaW5hIHBhbGFicmFzIGRlIHRyZXMgY2FyYWN0ZXJlcy4gUGFyZWNlIGhhYmVyIG3DoXMgc2lnbGFzIHkgcGFsYWJyYXMgY29ydGFzIHF1ZSB0aWVuZW4gc2VudGlkbyBwZXJvLCBkYWRhIGxhIGdyYW4gY2FudGlkYWQgZGUgc3RvcHdvcmRzIGRlIDMgY2FyYWN0ZXJlcywgaGF5IG1vdGl2b3MgcGFyYSBpbmNsdWlyIGVzdGUgcGFzby4KbnVldmFzX3N0b3B3b3Jkc18zIDwtIHRmX2lkZiAlPiUgCiAgc2VsZWN0KHdvcmQpICU+JSAKICBmaWx0ZXIoc3RyX2xlbmd0aCh3b3JkKSA9PSAzICYgISh3b3JkICVpbiUgYygnc2FuJywgJ21pbCcsICdsZXknLCAnYWZwJywgJ3N1cicsICdmbWknLCAndXNkJywgJ3JpbycsICdnb2wnLCAncGF6JywgJ21hcicsICdvcm8nLCAncmVkJywgJ2x1eicsICd2b3onICwgJ3JvbCcsICdzb2wnLCAnZ2FzJywgJ3BpZScsICdwYXInLCAncHJvJywgJ3ZpYScsICdvbnUnLCAneXBmJywgJ2l2YScsICdhZmEnLCAncGJpJywgJ2JhcicsICdjZmsnLCAnZWplJywgJ3JleScsICdhdHAnLCAnZG9uJywgJ2ZiaScsICdnYXknLCAncHNnJywgJ3ViYScsICd1Y3InLCAnY2VvJywgJ29uZycsICdmZWQnLCAnb2pvJywgJ2RlYScsICd1dmEnLCAnY2d0JywgJ3VmaScsICdhcHAnLCAnZ2lsJywgJ3ZpaCcsICduYmEnLCAnYmJjJywgJ2V2bycsICdoaXAnLCAnaG9wJywgJ2ZveCcsICduYmMnLCAncmFwJywgJ2FkbicsICdhbGEnLCAnZXZhJywgJ3BhbicsICd6ZW4nLCAnYWZ2JywgJ2NjaycsICdlY28nLCAnb2NhJywgJ3RpbycsICdjbm4nLCAnY2lhJywgImRuaSIsICJ1aWEiLCAiZmR0IiwgInVpZiIsICJoYm8iLCAibWxzIiwgIm9lYSIsICJtZXAiLCAiZ25jIiwgImF1aCIsICJjaGUiLCAib2lsIiwgImdlbiIsICJhZ24iLAkiZnB2IiwgImxhbSIsCSJwaWIiLCAiY25lIiwiZHVvIiwgInZveCIsICJkb3ciLCAidWNhIiwgImRudSIsICJwZXoiLCAicGZhIiwgInBkdCIsICJmZGEiLCAiZmNpIiwgIm9tcyIsICJwc2EiLCAiZmVvIiwgImN0YSIsICJlZ28iLAkJCQkKImZhYSIsCSJ1dGUiLCAiYXRlIiwJImxpbyIsICJmcHQiLCAic3V2IiwgImdiYSIsCSJpenEiLCAiYXJvIiwgInNtbiIsICJ0bnQiLAkidWNvIiwgImlwYyIsICJzYWEiLCAidG16IiwgImNjbCIsICJnZWwiLCAidmlwIiwgImVzaSIsCSJyZXMiLCAia3VuIiwgInRzaiIsICJhZmkiLAkicHRzIiwgImNuaCIsICJham8iLCAiYWN2IiwgImJtdyIsICJidXMiLCAiZ3BzIiwgImlsZSIsCSJpb3MiLCAidW5jIiwJInpvbyIsICJqdXAiLCAidG9zIiwgInVubCIsCSJ1cGwiLCAiemVvIiwgImF2ZSIsICJtdGUiLCAibXBuIiwgImFwbiIsICJtYW8iLCAicGJhIiwgInNtcyIsICJjbnYiLCAibWR6IiwgImZvbCIsICJpc28iKSkpICU+JSAKICBwdWxsKHdvcmQpCgpudWV2YXNfc3RvcHdvcmRzIDwtIGRhdGEuZnJhbWUod29yZCA9IG51ZXZhc19zdG9wd29yZHNfMSkgJT4lIAogIGJpbmRfcm93cyhkYXRhLmZyYW1lKHdvcmQgPSBudWV2YXNfc3RvcHdvcmRzXzIpKSAlPiUgCiAgYmluZF9yb3dzKGRhdGEuZnJhbWUod29yZCA9IG51ZXZhc19zdG9wd29yZHNfMykpCgpzdG9wX3dvcmRzX2Z1bGxfdjIgPC0gc3RvcF93b3Jkc19mdWxsICU+JSAKICBiaW5kX3Jvd3ModGliYmxlKHdvcmQgPSBjKCdlbWJlZCcsICdhbmlvJywgJ2FubycsICdhbm9zJywgJ2d1c3RhJywgJ3R3aXR0ZXInLCAnZmFjZWJvb2snLCAnY29tZW50YXInLCAnZnVlbnRlJywgJ3doYXRzYXBwJywgJ2d1YXJkYXInLCAnY29tcGFydGlyJywgJ21haWwnLCAnbG9hZGluZycsICdlbWFpbCcsICdwYWdpbmFpJywgJ2VsdHJlY2UnLCAnaW5mb2JhZScsICdodHRwJywgJ2h0dHBzJywgJ2F0dHJpYnV0ZScsICdmaW5kX2FsbCcsICdub25ldHlwZScsICdvYmplY3QnLCAncmVhZCcgKSkpICU+JSAKICBiaW5kX3Jvd3MobnVldmFzX3N0b3B3b3JkcykKYGBgCgpBIHBhcnRpciBkZSBsYSBudWV2YSBsaXN0YSBkZSBzdG9wd29yZHMsIHJlY3JlYW1vcyBlbCBjb3JwdXMgZW4gZm9ybWF0byB0aWR5IHkgdGFtYmnDqW4gZWwgZGF0YWZyYW1lIGVuIGZvcm1hdG8gbWVkaW8vdMOpcm1pbm8vbiBwYXJhIGNvbnRpbnVhciBjb24gZWwgYW7DoWxpc2lzIGRlIG3DqXRyaWNhcy4KCmBgYHtyIGNvcnB1c190aWR5X3YyfQpjb3JwdXNfdGlkeV92MiA8LSBjb3JwdXNfdGlkeSAlPiUgCiAgYW50aV9qb2luKHN0b3Bfd29yZHNfZnVsbF92MikKYGBgCgpgYGB7ciB3b3Jkc190aWR5X3YyfSAKd29yZHNfdGlkeV92MiA8LSBjb3JwdXNfdGlkeV92MiAlPiUgCiAgZ3JvdXBfYnkobWVkaW8sIHdvcmQpICU+JQogIHN1bW1hcmlzZShuPW4oKSkKCnRvdGFsX3dvcmRzX3YyIDwtIHdvcmRzX3RpZHlfdjIgJT4lCiAgICAgICAgZ3JvdXBfYnkobWVkaW8pICU+JQogICAgICAgIHN1bW1hcml6ZSh0b3RhbCA9IHN1bShuKSkgCgp3b3Jkc190aWR5X3YyIDwtIHdvcmRzX3RpZHlfdjIgJT4lIAogIGxlZnRfam9pbih0b3RhbF93b3Jkc192MikgJT4lIAogIHVuZ3JvdXAoKSAlPiUgCiAgYXJyYW5nZShkZXNjKG4pKQpgYGAKCiMjIyBSZXZpc2nDs24gZGUgbcOpdHJpY2FzCgpDcmVhbW9zIGVsIGdyw6FmaWNvIGNvbiBsYSBkaXN0cmlidWNpw7NuIGRlIHTDqXJtaW5vcyBzZWfDum4gc3UgdGYgcGFyYSBpZGVudGlmaWNhciB2YXJpYWNpb25lcyByZXNwZWN0byBhIGxhIHYxLgoKYGBge3IgdGZfdml6X21hbnVhbF92Mn0KdGZfdml6X3YyIDwtIHdvcmRzX3RpZHlfdjIgJT4lIG11dGF0ZSh0ZiA9IG4vdG90YWwpICU+JQogICAgICAgICAgZ2dwbG90KGFlcyh0ZikpICsKICAgICAgICAgICAgICAgIGdlb21faGlzdG9ncmFtKHNob3cubGVnZW5kID0gRkFMU0UpICsKICAgICAgICAgICAgICAgIGNvb3JkX2ZsaXAoKSsKICAgICAgICAgICAgICAgIHhsaW0oTkEsIDAuMDAwMikgKwogICAgICAgICAgICAgICAgZmFjZXRfd3JhcCh+bWVkaW8pICsKICAgICAgICAgICAgICAgIHRoZW1lX2NsYXNzaWMoKSsKICAgICAgICAgICAgICAgIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3Q9IDAuNSksCiAgICAgICAgICAgICAgICAgICAgICBheGlzLnRpdGxlID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICAgICAgICAgICAgYXhpcy50aWNrcy54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICAgICAgICAgICAgdGV4dCA9IGVsZW1lbnRfdGV4dChmYW1pbHkgPSAiQ291cmllciIpKQogICAgICAgICAgICAgICAgICAKZ2dwbG90bHkodGZfdml6X3YyKQpgYGAKCmBgYHtyIHRmX2lkZl92Mn0KdGZfaWRmX3YyIDwtIHdvcmRzX3RpZHlfdjIgJT4lIAogIGJpbmRfdGZfaWRmKHdvcmQsIG1lZGlvLCBuKSAKYGBgCgpDcmVhbW9zIGxvcyBkYXRhc2V0IG5lY2VzYXJpb3MgcGFyYSB2aXN1YWxpemFyIGxvcyBwcmluY2lwYWxlcyB0w6lybWlub3MgcG9yIG3DqXRyaWNhIHkgcG9yIG1lZGlvLCBidXNjYW5kbyBkaWZlcmVuY2lhcyByZXNwZWN0byBhIGxhIHYxLgoKYGBge3IgdG9wX3Rlbl9tZXRyaWNhc192Mn0KdGZfMTBfdjIgPC0gdGZfaWRmX3YyICU+JQogICAgICAgIGdyb3VwX2J5KG1lZGlvKSAlPiUKICAgICAgICBhcnJhbmdlKGRlc2ModGYpKSAlPiUKICAgICAgICBzbGljZV9oZWFkKG4gPSAxMCkgJT4lCiAgICAgICAgc2VsZWN0KG1lZGlvLCB3b3JkLCB0ZikgJT4lIAogICAgICAgIHJlbmFtZShuID0gdGYpICU+JQogICAgICAgIG11dGF0ZShuID0gbG9nKG4gKyAxKSAqIDEwMCkgJT4lIAogICAgICAgIG11dGF0ZShuID0gcm91bmQobiwgNCkpCgppZGZfMTBfdjIgPC0gdGZfaWRmX3YyICU+JQogICAgICAgICAgZ3JvdXBfYnkobWVkaW8pICU+JQogICAgICAgICAgYXJyYW5nZShkZXNjKGlkZikpICU+JQogICAgICAgICAgc2xpY2VfaGVhZChuID0gMTApICU+JQogICAgICAgICAgc2VsZWN0KG1lZGlvLCB3b3JkLCBpZGYpICU+JQogICAgICAgICAgcmVuYW1lKG4gPSBpZGYpICU+JQogICAgICAgICAgbXV0YXRlKG4gPSBsb2cobiArIDEpICogMTAwKSAlPiUgCiAgICAgICAgICBtdXRhdGUobiA9IHJvdW5kKG4sIDQpKQoKdGZfaWRmXzEwX3YyIDwtIHRmX2lkZl92MiAlPiUKICAgICAgICBncm91cF9ieShtZWRpbykgJT4lCiAgICAgICAgYXJyYW5nZShkZXNjKGlkZikpICU+JQogICAgICAgIHNsaWNlX2hlYWQobiA9IDEwKSAlPiUgCiAgICAgICAgc2VsZWN0KG1lZGlvLCB3b3JkLCB0Zl9pZGYpICU+JSAKICAgICAgICByZW5hbWUobiA9IHRmX2lkZikgJT4lCiAgICAgICAgbXV0YXRlKG4gPSBsb2cobiArIDEpICogMTAwKSAlPiUgCiAgICAgICAgbXV0YXRlKG4gPSByb3VuZChuLCA0KSkKYGBgCgpgYGB7ciB0Zl92aXpfdjJ9CmNyZWFyX2dyYWZfd29yZHModGZfMTBfdjIpIApgYGAKCmBgYHtyIGlkZl92aXpfdjJ9CmNyZWFyX2dyYWZfd29yZHMoaWRmXzEwX3YyKQpgYGAKCmBgYHtyIHRmX2lkZl92aXpfdjJ9CmNyZWFyX2dyYWZfd29yZHModGZfaWRmXzEwX3YyKQpgYGAKCkFuw6FsaXNpcyBmaW5hbCBkZSBURi1JREY6CgoKLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiMjIE1vZGVsYWRvIGRlIHTDs3BpY29zOiBMREEKCsK/Q3XDoWxlcyBzb24gbG9zIHTDs3BpY29zIHByaW5jaXBhbGVzIGVuIGVsIGNvcnB1cz8gwr9QdWVkZW4gZXZpZGVuY2lhciBkaWZlcmVuY2lhcyBlbiBjYWRhIHVubyBkZSBsb3MgbWVkaW9zPyBFeHBsaWNhciBxdcOpIG3DqXRvZG8gc2UgdXRpbGl6w7MgcGFyYSByZXNwb25kZXIgbGEgcHJlZ3VudGEsIGN1w6FsZXMgc29uIGxvcyBzdXB1ZXN0b3MgZGVsIG1pc21vLiBHZW5lcmFyIGxhcyB2aXN1YWxpemFjaW9uZXMgbcOhcyBhZGVjdWFkYXMgcGFyYSByZXNwb25kZXIgYSBsYXMgcHJlZ3VudGFzLgoKUGFyYSBpbXBsZW1lbnRhciB1biBtb2RlbGFkbyBkZSB0ZW1hcyBjb24gTERBIG5lY2VzaXRhbW9zIGNvbnN0cnVpciB1bmEgbWF0cml6IERUTS4KCmBgYHtyIGR0bV8yfQpwYXJhX2Rpc2NfZHRtIDwtIGNvcnB1c190aWR5X3YyICU+JSAKICBncm91cF9ieShpZCwgd29yZCkgJT4lCiAgc3VtbWFyaXNlKG49bigpKQojIGNyZW8gdW4gY29udGVvIGRlIHBhbGFicmFzIHBvciBub3RpY2lhLCBubyBwb3IgbWVkaW8gCgpkaXNjX2R0bV8yIDwtIHBhcmFfZGlzY19kdG0gJT4lCiAgICAgICAgICAgICAgICBjYXN0X2R0bShpZCwgd29yZCwgbikKCmBgYAoKRW4gcHJpbWVyIGx1Z2FyIGludGVudGFtb3MgZGV0ZWN0YXIgNSB0w7NwaWNvczoKYGBge3IgbGRhXzV9CmxkYV81IDwtIExEQShkaXNjX2R0bV8yLCBrID0gNSwgY29udHJvbCA9IGxpc3Qoc2VlZCA9IDU1NSkpCgp3cml0ZV9yZHMobGRhXzUsIm1vZGVsb3MvbGRhXzUucmRzIikKCmxkYV81IDwtIHJlYWRfcmRzKCJtb2RlbG9zL2xkYV81LnJkcyIpCgphcF90b3BpY3NfNSA8LSB0aWR5KGxkYV81LCBtYXRyaXggPSAiYmV0YSIpIAoKYXBfdG9waWNzXzUgJT4lCiAgbXV0YXRlKGJldGEgPSByb3VuZCgxMDAqYmV0YSw2KSkKCmFwX3RvcF90ZXJtc181IDwtIGFwX3RvcGljc181ICU+JQogIGdyb3VwX2J5KHRvcGljKSAlPiUKICBzbGljZV9tYXgoYmV0YSwgbiA9IDE1KSAlPiUgCiAgdW5ncm91cCgpICU+JQogIGFycmFuZ2UodG9waWMsIC1iZXRhKQoKYXBfdG9wX3Rlcm1zXzUgJT4lCiAgbXV0YXRlKHRlcm0gPSByZW9yZGVyX3dpdGhpbih0ZXJtLCBiZXRhLCB0b3BpYykpICU+JQogIGdncGxvdChhZXMoYmV0YSwgdGVybSwgZmlsbCA9IGZhY3Rvcih0b3BpYykpKSArCiAgZ2VvbV9jb2woc2hvdy5sZWdlbmQgPSBGQUxTRSkgKwogIGZhY2V0X3dyYXAofiB0b3BpYywgc2NhbGVzPSdmcmVlX3knKSArCiAgc2NhbGVfeV9yZW9yZGVyZWQoKSsKICB0aGVtZV9taW5pbWFsKCkrCiAgbGFicyh0aXRsZSA9ICJQYWxhYnJhcyBtw6FzIGltcG9ydGFudGVzIGVuIDUgdMOzcGljb3MgTERBIikgCiAgICAgIApgYGAKCkxhIGV4cGxvcmFjacOzbiBkZSB0w6lybWlub3MgcG9yIHTDs3BpY28gc2Vnw7puIGVsIGJldGEgKHByb2JhYmlsaWRhZCBkZSBwZXJ0ZW5lbmNpYSBhIHVuIHTDs3BpY28pIHBlcm1pdGUgYXNvY2lhciB0ZW3DoXRpY2FzIGEgY2FkYSB1bm8gZGUgZWxsb3M6CgoxLSBQb2zDrXRpY2EgTmFjaW9uYWwvIEVsZWNjaW9uZXMKMi0gRGVwb3J0ZXMKMy0gU29jaWVkYWQKNC0gUG9sw610aWNhIEludGVybmFjaW9uYWwKNS0gRWNvbm9tw61hCgpBIGNvbnRpbnVhY2nDs24sIGhhY2Vtb3MgdW5hIG51ZXZhIHBydWViYSBwYXJhIGV4cGxvcmFyIHNpIGVzIHBvc2libGUgaWRlbnRpZmljYXIgdW5hIG1heW9yIGNhbnRpZGFkIGRlIHTDs3BpY29zIChrPTEwKToKCmBgYHtyIGxkYV8xMH0KbGRhXzEwIDwtIExEQShkaXNjX2R0bV8yLCBrID0gMTAsIGNvbnRyb2wgPSBsaXN0KHNlZWQgPSAxMDEwKSkgIAoKd3JpdGVfcmRzKGxkYV8xMCwibW9kZWxvcy9sZGFfMTAucmRzIikKCmxkYV8xMCA8LSByZWFkX3JkcygibW9kZWxvcy9sZGFfMTAucmRzIikKCmFwX3RvcGljc18xMCA8LSB0aWR5KGxkYV8xMCwgbWF0cml4ID0gImJldGEiKSAKCmFwX3RvcGljc18xMCAlPiUKICBtdXRhdGUoYmV0YSA9IHJvdW5kKDEwMCpiZXRhLDYpKQoKYXBfdG9wX3Rlcm1zXzEwIDwtIGFwX3RvcGljc18xMCAlPiUKICBncm91cF9ieSh0b3BpYykgJT4lCiAgc2xpY2VfbWF4KGJldGEsIG4gPSAxNSkgJT4lIAogIHVuZ3JvdXAoKSAlPiUKICBhcnJhbmdlKHRvcGljLCAtYmV0YSkKCmFwX3RvcF90ZXJtc18xMCAlPiUKICBtdXRhdGUodGVybSA9IHJlb3JkZXJfd2l0aGluKHRlcm0sIGJldGEsIHRvcGljKSkgJT4lCiAgZ2dwbG90KGFlcyhiZXRhLCB0ZXJtLCBmaWxsID0gZmFjdG9yKHRvcGljKSkpICsKICBnZW9tX2NvbChzaG93LmxlZ2VuZCA9IEZBTFNFKSArCiAgZmFjZXRfd3JhcCh+IHRvcGljLCBzY2FsZXM9J2ZyZWVfeScpICsKICBzY2FsZV95X3Jlb3JkZXJlZCgpICsKICB0aGVtZV9taW5pbWFsKCkrCiAgbGFicyh0aXRsZSA9ICJQYWxhYnJhcyBtw6FzIGltcG9ydGFudGVzIGVuIDEwIHTDs3BpY29zIExEQSIpIApgYGAKCkNvbiBlc3RhIHBydWViYSwgcXVlIGR1cGxpY8OzIGxhIGNhbnRpZGFkIGRlIHTDs3BpY29zLCBjb21lbnphbW9zIGEgcmVnaXN0cmFyIGNvbmp1bnRvcyBxdWUgbm8gdGllbmVuIHVuIHNlbnRpZG8gdGFuIGRlZmluaWRvLCBjYXRhbG9nYWRvcyBwcm92aXNvcmlhbWVudGUgY29tbyB0w7NwaWNvcyBkZSAiU29jaWVkYWQiICgxLCAzIHkgNikuIEEgc3UgdmV6IGVuIGRvcyBjYXNvcyBlcyBwb3NpYmxlIHJlY29ub2NlciB1bmEgc3VwZXJwb3NpY2nDs24gdGVtw6F0aWNhICg0IHkgOCkuCgoxLSBTb2NpZWRhZAoyLSBQb2zDrXRpY2EgTmFjaW9uYWwvIEVsZWNjaW9uZXMKMy0gU29jaWVkYWQgKMK/VmlkYSBGYW1pbGlhcj8pCjQtIFBvbMOtdGljYSBJbnRlcm5hY2lvbmFsCjUtIFBvbGljaWFsZXMKNi0gU29jaWVkYWQKNy0gRGVwb3J0ZQo4LSBQb2zDrXRpY2EgSW50ZXJuYWNpb25hbAo5LSBFY29ub23DrWEKMTAtIEN1bHR1cmEvIENpbmUgeSBTZXJpZXMKCkVuIGVzYSBkaXJlY2Npw7NuLCBhdmFuemFtb3MgZW4gdW5hIG51ZXZhIHBydWViYSBxdWUgcHJvcG9uZSByZWR1Y2lyIGxldmVtZW50ZSBsYSBjYW50aWRhZCBkZSB0w7NwaWNvcyAoaz04KToKCmBgYHtyIGxkYV84fQpsZGFfOCA8LSBMREEoZGlzY19kdG1fMiwgayA9IDgsIGNvbnRyb2wgPSBsaXN0KHNlZWQgPSA4ODgpKSAKCndyaXRlX3JkcyhsZGFfOCwibW9kZWxvcy9sZGFfOC5yZHMiKQoKbGRhXzggPC0gcmVhZF9yZHMoIm1vZGVsb3MvbGRhXzgucmRzIikKCmFwX3RvcGljc184IDwtIHRpZHkobGRhXzgsIG1hdHJpeCA9ICJiZXRhIikgCgphcF90b3BpY3NfOCAlPiUKICBtdXRhdGUoYmV0YSA9IHJvdW5kKDEwMCpiZXRhLDYpKQoKYXBfdG9wX3Rlcm1zXzggPC0gYXBfdG9waWNzXzggJT4lCiAgZ3JvdXBfYnkodG9waWMpICU+JQogIHNsaWNlX21heChiZXRhLCBuID0gMTUpICU+JSAKICB1bmdyb3VwKCkgJT4lCiAgYXJyYW5nZSh0b3BpYywgLWJldGEpCgphcF90b3BfdGVybXNfOCAlPiUKICBtdXRhdGUodGVybSA9IHJlb3JkZXJfd2l0aGluKHRlcm0sIGJldGEsIHRvcGljKSkgJT4lCiAgZ2dwbG90KGFlcyhiZXRhLCB0ZXJtLCBmaWxsID0gZmFjdG9yKHRvcGljKSkpICsKICBnZW9tX2NvbChzaG93LmxlZ2VuZCA9IEZBTFNFKSArCiAgZmFjZXRfd3JhcCh+IHRvcGljLCBzY2FsZXM9J2ZyZWVfeScpICsKICBzY2FsZV95X3Jlb3JkZXJlZCgpICsKICB0aGVtZV9taW5pbWFsKCkrCiAgbGFicyh0aXRsZSA9ICJQYWxhYnJhcyBtw6FzIGltcG9ydGFudGVzIGVuIDggdMOzcGljb3MgTERBIikgCmBgYAoKRW4gY29tcGFyYWNpw7NuIGNvbiBsYSBwcnVlYmEgYW50ZXJpb3IsIHRhbWJpw6luIGRldGVjdGFtb3MgdHJlcyB0w7NwaWNvcyBjb24gdW4gc2VudGlkbyBkaWZ1c28gKDEsIDMgeSA3KSwgbWllbnRyYXMgcXVlIGRlc2FwYXJlY2llcm9uIHRhbnRvIGxhIHJlcGV0aWNpw7NuIGRlbCB0w7NwaWNvICJQb2zDrXRpY2EgSW50ZXJuYWNpb25hbCIgY29tbyBlbCB0w7NwaWNvICJDdWx0dXJhLyBDaW5lIHkgU2VyaWVzIi4KCjEtIFNvY2llZGFkCjItIEVjb25vbcOtYQozLSBTb2NpZWRhZAo0LSBQb2xpY2lhbGVzCjUtIFBvbMOtdGljYSBJbnRlcm5hY2lvbmFsCjYtIERlcG9ydGVzCjctIFNvY2llZGFkICjCv1kgQ3VsdHVyYT8pCjgtIFBvbMOtdGljYSBOYWNpb25hbC8gRWxlY2Npb25lcwoKVGVuaWVuZG8gZW4gY3VlbnRhIGxhcyB0cmVzIHBydWViYXMgcmVhbGl6YWRhcyBjb24gZWwgbW9kZWxvIExEQSwgZW50ZW5kZW1vcyBxdWUgZWwgbWVqb3IgbW9kZWxvIGVzIGVsIHF1ZSBwbGFudGVhIHVuIEs9NS4gTGEgdmlzdWFsaXphY2nDs24gY29ycmVzcG9uZGllbnRlIGEgaz01IG11ZXN0cmEgcXVlIGFsZ3VuYXMgcGFsYWJyYXMgY29tbyAiYXJnZW50aW5hIiBvICJwZXJzb25hcyIgc29uIGNvbXVuZXMgYSBtw6FzIGRlIHVuIHRlbWEuIEVzIGRlY2lyLCBxdWUgbG9zIHTDs3BpY29zIGlkZW50aWZpY2Fkb3MgdGllbmVuIGNpZXJ0YSBzdXBlcnBvc2ljacOzbiBlbiB0w6lybWlub3MgZGUgcGFsYWJyYXMuIENvbW8gYWx0ZXJuYXRpdmEgcG9kcsOtYW1vcyBjb25zaWRlcmFyIGxvcyB0w6lybWlub3MgcXVlIHR1dmllcmFuIGxhIG1heW9yIGRpZmVyZW5jaWEgZW4gzrIgZW50cmUgZWwgdGVtYSAxIHkgZWwgdGVtYSA1IChxdWUgc29uIGRvcyBkZSBsb3MgcXVlIG1lam9yIHBvZGVtb3MgaW50ZXJwcmV0YXIpLgoKRFVEQTogZW4gZWwgZWplbXBsbyBxdWUgYXBhcmVjZSBlbiBsYSBub3RlYm9vaywgaGF5IGRvcyB0ZW1hcyBxdWUgbm8gdGllbmVuIHVuIHNlbnRpZG8gZGVmaW5pZG8geSBwb3IgZXNvICJFc3RvIHBhcmVjZSB1biBwcmltZXIgaW5kaWNhZG9yIGRlIHF1ZSBkZWJlcsOtYW1vcyBjb25zaWRlcmFyIGxhIHBvc2liaWxpZGFkIGRlIHV0aWxpemFyIHVuIG7Dum1lcm8gZGUgdMOzcGljb3MgbcOhcyBlbGV2YWRvIi4gRW4gbnVlc3RybyBjYXNvIGxvIGVzdG95IHBlbnNhbmRvIGFsIHJldsOpcyAobWVub3MgaywgbWF5b3IgZGVmaW5pY2nDs24gZGUgdMOzcGljb3MpLgoKYGBge3IgYmV0YV9sZGFfNX0KYmV0YV93aWRlIDwtIGFwX3RvcGljc181ICU+JQogIG11dGF0ZSh0b3BpYyA9IHBhc3RlMCgidG9waWMiLCB0b3BpYykpICU+JQogIHBpdm90X3dpZGVyKG5hbWVzX2Zyb20gPSB0b3BpYywgdmFsdWVzX2Zyb20gPSBiZXRhKSAlPiUgCiAgZmlsdGVyKHRvcGljMSA+IC4wMDIgfCB0b3BpYzUgPiAuMDAyKSAlPiUKICBtdXRhdGUobG9nX3JhdGlvMV81ID0gbG9nMih0b3BpYzUgLyB0b3BpYzEpKQoKYmV0YV93aWRlICU+JQogIGdncGxvdChhZXMoeD1yZW9yZGVyKHRlcm0sbG9nX3JhdGlvMV81KSAsIHk9bG9nX3JhdGlvMV81KSkgKwogICAgZ2VvbV9jb2woKSArCiAgICBjb29yZF9mbGlwKCkgKwogICAgbGFicyh4PSdUw6lybWlubycsCiAgICAgICAgIHk9J0xvZzIgcmF0aW8gdG9waWM1L3RvcGljMScpICsKICAgIHRoZW1lX21pbmltYWwoKQpgYGAKClBhbGFicmFzIGNvbW8g4oCcdmlkYWwiLCAianVzdGljaWEiIG8gImtpcmNobmVyIiBjYXJhY3Rlcml6YW4gYWwgdMOzcGljbyAxIChQb2zDrXRpY2EgTmFjaW9uYWwpLCBtaWVudHJhcyBxdWUg4oCccHJvZHVjdG9z4oCdLCDigJxpbmZsYWNpw7Nu4oCdIG8gIm1lcmNhZG8iIHJlcHJlc2VudGFuIGFsIHTDs3BpY28gNSAoRWNvbm9tw61hKS4gRXN0byBjb250cmlidXllIGEgY29uZmlybWFyIHF1ZSBzZSB0cmF0YSBkZSBkb3MgdMOzcGljb3MgYmllbiBkaWZlcmVuY2lhZG9zIHBvciBlbCBtb2RlbG8uCgpZYSBpZGVudGlmaWNhZG9zIDUgdMOzcGljb3MgeSBjb25maXJtYWRhIGxhIGRpZmVyZW5jaWFjacOzbiBlbnRyZSBlbGxvcyBwb2RlbW9zIHV0aWxpemFyIGxvcyByZXN1bHRhZG9zIGRlbCBtb2RlbG8gcGFyYSByZWxhY2lvbmFyIGNhZGEgbm90aWNpYSBjb24gdW4gdMOzcGljby4gTGEgbWF0cml6IGdhbW1hIGVzdGFibGVjZSBsYSBwcm9iYWJpbGlkYWQgZGUgY2FkYSBkb2N1bWVudG8gYSBwZXJ0ZW5lY2VyIGEgdW4gdMOzcGljby4gRW4gcGFydGljdWxhciBub3Mgc2lydmUgcGFyYSBhc29jaWFyIHTDs3BpY29zIGNvbiBjYWRhIHVubyBkZSBsb3MgbWVkaW9zIGEgbG9zIHF1ZSBwZXJ0ZW5lY2VuIGxhcyBub3RpY2lhcy4KCmBgYHtyIGdhbW1hX2xkYV81fQpkb2NfdG9waWNzX2xkYSA8LSB0aWR5KGxkYV81LCBtYXRyaXggPSAiZ2FtbWEiKQoKZG9jX3RvcGljc19sZGEgJT4lCiAgbXV0YXRlKGdhbW1hID0gcm91bmQoZ2FtbWEsIDUpLAogICAgICAgICBkb2N1bWVudCA9IGFzLmludGVnZXIoZG9jdW1lbnQpKSAlPiUKICBhcnJhbmdlKGRvY3VtZW50LCBkZXNjKGdhbW1hKSkKYGBgCgpDYWRhIHVubyBkZSBlc3RvcyB2YWxvcmVzIGVzIHVuYSBwcm9wb3JjacOzbiBlc3RpbWFkYSBkZSBwYWxhYnJhcyBkZSBlc2UgZG9jdW1lbnRvIHF1ZSBzZSBnZW5lcmFuIGEgcGFydGlyIGRlIGVzZSB0ZW1hLiBFbiBidXNjYSBkZSB2YWxpZGFyIHF1ZSBlbCBtb2RlbG8gaWRlbnRpZmljbyBjbGFyYW1lbnRlIHVuIHTDs3BpY28gYXNvY2lhZG8gYSBQb2zDrXRpY2EgTmFjaW9uYWwsIHZhbW9zIGEgcmV2aXNhciBlbCBkb2N1bWVudG8gcXVlIG1heW9yIHByb2JhYmlsaWRhZCB0aWVuZSBkZSBleHByZXNhcmxvLgoKYGBge3IgZG9jX3RvcGljX2xkYX0KZG9jX3RvcGljc19sZGEgJT4lCiAgZmlsdGVyKHRvcGljID09IDEgfCB0b3BpYyA9PSA1KSAlPiUgCiAgbXV0YXRlKGdhbW1hID0gcm91bmQoZ2FtbWEsIDUpKSAlPiUgCiAgYXJyYW5nZShkZXNjKGdhbW1hKSkKYGBgCgpFbiBwYXJ0aWN1bGFyLCBzaSBvYnNlcnZhbW9zIGVsIGRvY3VtZW50byA5NCwgbGEgcHJvYmFiaWxpZGFkIGdhbW1hIGFzb2NpYWRhIGFsIHRlbWEgMSBlcyAwLjk5ODc4LCBsbyBxdWUgc2lnbmlmaWNhIHF1ZSBlbCBtb2RlbG8gZXN0aW1hIHF1ZSBhbHJlZGVkb3IgZGVsIDk5JSBkZSBsYXMgcGFsYWJyYXMgZW4gZWwgZG9jdW1lbnRvIDk0IHNlIGdlbmVyYXJvbiBhIHBhcnRpciBkZWwgdGVtYSAxLiBQb3IgbG8gdGFudG8sIHBvZGVtb3MgY29uY2x1aXIgcXVlIGVsIHRlbWEgMSBlcyBhbHRhbWVudGUgcmVsZXZhbnRlIHBhcmEgZWwgZG9jdW1lbnRvIDk0IHNlZ8O6biBlbCBtb2RlbG8uCgpgYGB7ciBsZGFfdG9waWNfMV9kb2N9CmNvcnB1c190aWR5X3YyJT4lCiAgZmlsdGVyKGlkPT05NCkgJT4lCiAgZ3JvdXBfYnkoaWQsIHdvcmQpICU+JQogIHN1bW1hcmlzZShuPW4oKSkgJT4lCiAgc2VsZWN0KHdvcmQsIG4pICU+JQogIGFycmFuZ2UoZGVzYyhuKSkKYGBgCgpTZSB2ZSBjb21vIGVuIGVzdGEgbm90aWNpYSBwYXJlY2VuIHByZWRvbWluYXIgcGFsYWJyYXMgZGVsIHTDs3BpY28gMSAoIm5hY2lvbmFsIiwgImVsZWNjaW9uZXMiKS4gVmVhbW9zIGVsIHRleHRvIGNvbXBsZXRvIGRlIGVzdGUgZG9jdW1lbnRvOgoKYGBge3IgbGRhX3RvcGljXzFfZG9jXzk0fQpjb3JwdXNfYmFzZSAlPiUKICBmaWx0ZXIoaWQ9PTk0KSAlPiUKICBzZWxlY3QodGl0dWxvLCB0ZXh0bykgJT4lCiAgcHVsbCgpCmBgYAoKTGEgbm90aWNpYSAoaWQ9OTQpIGhhYmxhIGRlIGxhcyBlbGVjY2lvbmVzIHksIGVuIHBhcnRpY3VsYXIsIGRlIGxhcyBlbGVjY2lvbmVzIGVuIGxhIHByb3ZpbmNpYSBkZSBNZW5kb3phLgoKQSBjb250aW51YWNpw7NuIHV0aWxpemFtb3MgbGEgbWF0cml6IGRlIHTDs3BpY29zIHBvciBkb2N1bWVudG8gcGFyYSBqb2luZWFyIGNvbiBlbCBtZWRpbyBhbCBxdWUgcGVydGVuZWNlIGxhIG5vdGljaWEgeSBmaW5hbG1lbnRlIAp2aXN1YWxpemFyIGxhIHByZWRvbWluYW5jaWEgZGUgdMOzcGljb3MgcG9yIG1lZGlvOgoKYGBge3IgbGRhXzVfdG9waWNzX21lZGlvfQpkb2NfdG9waWNzX2xkYSAlPiUKICByZW5hbWUoaWQgPSBkb2N1bWVudCkgJT4lCiAgbXV0YXRlKHRvcGljID0gY2FzZV93aGVuKAogICAgICAgICAgICAgdG9waWMgPT0gMSB+ICJQb2zDrXRpY2EgTmFjaW9uYWwvRWxlY2Npb25lcyIsCiAgICAgICAgICAgICB0b3BpYyA9PSAyIH4gIkRlcG9ydGVzIiwKICAgICAgICAgICAgIHRvcGljID09IDMgfiAiU29jaWVkYWQiLAogICAgICAgICAgICAgdG9waWMgPT0gNCB+ICJQb2zDrXRpY2EgSW50ZXJuYWNpb25hbCIsCiAgICAgICAgICAgICB0b3BpYyA9PSA1IH4gIkVjb25vbcOtYSIpKSAgJT4lIAogIG11dGF0ZShpZCA9IGFzLmludGVnZXIoaWQpKSAlPiUKICBsZWZ0X2pvaW4oY29ycHVzX2Jhc2UgJT4lIGZpbHRlcihpZCAhPSA5MzkwKSAlPiUgc2VsZWN0KGlkLCBtZWRpbykgJT4lIHVuaXF1ZSgpKSAlPiUKICBncm91cF9ieShtZWRpbywgdG9waWMpICU+JQogICAgc3VtbWFyaXNlKG1lYW4gPSBtZWFuKGdhbW1hKSoxMDApICU+JQogIGdncGxvdCgpICsKICAgIGdlb21fY29sKGFlcyh4PXRvcGljLCB5PW1lYW4sIGZpbGw9bWVkaW8pLCBwb3NpdGlvbj0nZG9kZ2UnKSArCiAgICB0aGVtZV9taW5pbWFsKCkrCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSkrCiAgbGFicyh0aXRsZSA9ICJQcmVkb21pbmFuY2lhIGVuIG1lZGlvcyBkZSA1IHTDs3BpY29zIExEQSIpIApgYGAKClJlc3VsdGFkb3MgKGludGVycHJldGFjacOzbiBhIGRlc2Fycm9sbGFyKQpFcyBwb3NpYmxlIHJlY29ub2NlciBkaWZlcmVuY2lhcyBlbiBsYSBwcmV2YWxlbmNpYSBkZSBsb3MgdMOzcGljb3MgcGFyYSBjYWRhIHVubyBkZSBsb3MgbWVkaW9zLCBjb24gZXhjZXBjacOzbiBkZWwgdMOzcGljbyAiRWNvbm9tw61hIiBxdWUgbXVlc3RyYSB1bmEgcHJldmFsZW5jaWEgc2ltaWxhciBlbiBjdWF0cm8gbWVkaW9zLgoKMS0gUG9sw610aWNhIE5hY2lvbmFsL0VsZWNjaW9uZXMgPSBUw6lsYW0KMi0gRGVwb3J0ZXMgPSBDcsOzbmljYQozLSBTb2NpZWRhZCA9IExhIE5hY2nDs24KNC0gUG9sw610aWNhIEludGVybmFjaW9uYWwgPSBJbmZvYmFlCjUtIEVjb25vbcOtYSA9IFTDqWxhbSwgUMOhZ2luYSAxMiwgUGVyZmlsIHkgQ2xhcsOtbgoKIyMgTW9kZWxhZG8gZGUgdMOzcGljb3M6IFNUTQoKUGFyYSBpbXBsZW1lbnRhciB1biBtb2RlbGFkbyBkZSB0ZW1hcyBjb24gU1RNIG5lY2VzaXRhbW9zIGNvbnN0cnVpciB1bmEgbWF0cml6IERGTS4KYGBge3IgZGZtfQpwYXJhX2Rpc2NfZGZtIDwtIGNvcnB1c190aWR5X3YyICU+JSAKICBncm91cF9ieShpZCwgd29yZCkgJT4lCiAgc3VtbWFyaXNlKG49bigpKQoKZGlzY19kZm0gPC0gcGFyYV9kaXNjX2RmbSAlPiUKICAgICAgICAgICAgICAgIGNhc3RfZGZtKGlkLCB3b3JkLCBuKQoKZGlzY19kZm0KYGBgCgpBIGRpZmVyZW5jaWEgZGVsIG1vZGVsbyBMREEsIFNUTSBwZXJtaXRlIGluY29ycG9yYXIgbWV0YWRhdGEgcXVlIHBvZHLDrWEgaW5mbHVlbmNpYXIgbGEgZGV0ZWNjacOzbiBkZSB0w7NwaWNvcy4gRW4gZXN0ZSBjYXNvIHN1bWFtb3MgY29tbyBjb3ZhcmlhYmxlIHBhcmEgY2FkYSBkb2N1bWVudG8gYSBxdcOpIG1lZGlvIHBlcnRlbmVjZS4KYGBge3IgbWV0YWRhdGF9Cm1ldGFkYXRhIDwtIGNvcnB1c190aWR5X3YyICU+JSAKICBzZWxlY3QoaWQsIG1lZGlvKSAlPiUgCiAgZGlzdGluY3QoKSAlPiUgCiAgbGVmdF9qb2luKGNvcnB1c19iYXNlICU+JSBzZWxlY3QoaWQsIHRleHRvKSkKCmBgYAoKQWwgaWd1YWwgcXVlIGNvbiBMREEsIGNvbWVuemFtb3MgcGlkaWVuZG9sZSBhbCBtb2RlbG8gcXVlIGVuY3VlbnRyZSA1IHTDs3BpY29zIGVuIGVsIGNvcnB1czoKYGBge3Igc3RtXzV9CnN0bV81IDwtIHN0bShkb2N1bWVudHMgPSBkaXNjX2RmbSwKICAgICAgSyA9IDUsCiAgICAgIHByZXZhbGVuY2UgPSB+bWVkaW8sCiAgICAgIG1heC5lbS5pdHMgPSA3NSwgCiAgICAgIGRhdGEgPSBtZXRhZGF0YSwKICAgICAgaW5pdC50eXBlID0gIlNwZWN0cmFsIiwKICAgICAgc2VlZCA9IDU1NSkKCndyaXRlX3JkcyhzdG1fNSwibW9kZWxvcy9zdG1fNS5yZHMiKQpgYGAKCmBgYHtyIHN0bV81X3ByZX0Kc3RtXzUgPC0gcmVhZF9yZHMoIm1vZGVsb3Mvc3RtXzUucmRzIikKYGBgCgpWYW1vcyBhIGdlbmVyYXIgZG9zIG1hdHJpY2VzIHBhcmEgZXhwbG9yYXIgbG9zIHJlc3VsdGFkb3MgZGVsIG1vZGVsbzogcGFsYWJyYXMgcG9yIHTDs3BpY28geSBkb2N1bWVudG9zIHBvciB0w7NwaWNvCgpgYGB7ciBtYXRyaXpfc3RtXzV9CiMgbWF0cml6IGRlIHTDqXJtaW5vcyBwb3IgdMOzcGljb3MKdGVybWlub3Nfc3RtXzUgPC0gdGlkeShzdG1fNSwgbWF0cml4ID0gJ2JldGEnKQpgYGAKCmBgYHtyIHN0bV81X3Rlcm1pbm9zfQogdGVybWlub3Nfc3RtXzUgJT4lCiAgZ3JvdXBfYnkodG9waWMpICU+JQogIHNsaWNlX21heChiZXRhLCBuID0gMTUpICU+JSAKICB1bmdyb3VwKCkgJT4lCiAgYXJyYW5nZSh0b3BpYywgLWJldGEpICU+JQogIG11dGF0ZSh0ZXJtID0gcmVvcmRlcl93aXRoaW4odGVybSwgYmV0YSwgdG9waWMpKSAlPiUKICBnZ3Bsb3QoYWVzKGJldGEsIHRlcm0sIGZpbGwgPSBmYWN0b3IodG9waWMpKSkgKwogIGdlb21fY29sKHNob3cubGVnZW5kID0gRkFMU0UpICsKICBmYWNldF93cmFwKH4gdG9waWMsIHNjYWxlcz0nZnJlZV95JykgKwogIHNjYWxlX3lfcmVvcmRlcmVkKCkgKwogIHRoZW1lX21pbmltYWwoKSsKICBsYWJzKHRpdGxlID0gIlBhbGFicmFzIG3DoXMgaW1wb3J0YW50ZXMgZW4gNSB0w7NwaWNvcyBTVE0iKSAKYGBgCkRlIG1hbmVyYSBzaW1pbGFyIGFsIExEQSwgcG9kZW1vcyBpZGVudGlmaWNhciB0w7NwaWNvcyBhc29jaWFkb3MgYSBQb2zDrXRpY2EgTmFjaW9uYWwsIGEgRGVwb3J0ZXMsIGEgRWNvbm9tw61hIHkgYSBTb2NpZWRhZC4gTm8gZGlzdGluZ3VlIGNvbnRlbmlkb3MgcmVsYWNpb25hZG9zIGEgcG9sw610aWNhIGludGVybmFjaW9uYWwgcGVybyBhcGFyZWNlIHVuIHTDs3BpY28ganVkaWNpYWwvcG9saWNpYWwuCgoxLSBQb2zDrXRpY2EgTmFjaW9uYWwvRWxlY2Npb25lcwoyLSBFY29ub23DrWEKMy0gSnVzdGljaWEvUG9saWNpYWwKNC0gU29jaWVkYWQKNS0gRGVwb3J0ZXMKCkEgbW9kbyBkZSBwcnVlYmEsIHkgY29uc2lkZXJhbmRvIHF1ZSBTVE0gcHVkbyBpZGVudGlmaWNhciB1biBudWV2byB0w7NwaWNvLCBjb3JyZW1vcyBlbCBtb2RlbG8gY29uIHVuIGsgbWF5b3IuCmBgYHtyIHN0bV84fQojIE5vIGNvcnJlcgpzdG1fOCA8LSBzdG0oZG9jdW1lbnRzID0gZGlzY19kZm0sCiAgICAgIEsgPSA4LAogICAgICBwcmV2YWxlbmNlID0gfm1lZGlvLAogICAgICBtYXguZW0uaXRzID0gNzUsIAogICAgICBkYXRhID0gbWV0YWRhdGEsCiAgICAgIGluaXQudHlwZSA9ICJTcGVjdHJhbCIsCiAgICAgIHNlZWQgPSA4ODgpCgp3cml0ZV9yZHMoc3RtXzgsIm1vZGVsb3Mvc3RtXzgucmRzIikKYGBgCgpgYGB7ciBzdG1fOF99CnN0bV84IDwtIHJlYWRfcmRzKCJtb2RlbG9zL3N0bV84LnJkcyIpCmBgYAoKYGBge3IgbWF0cml6X3N0bV84fQojIG1hdHJpeiBkZSB0w6lybWlub3MgcG9yIHTDs3BpY29zCgp0ZXJtaW5vc19zdG1fOCA8LSB0aWR5KHN0bV84LCBtYXRyaXggPSAnYmV0YScpCgpgYGAKCmBgYHtyIHN0bV84X3Rlcm1pbm9zfQogdGVybWlub3Nfc3RtXzggJT4lCiAgZ3JvdXBfYnkodG9waWMpICU+JQogIHNsaWNlX21heChiZXRhLCBuID0gMTUpICU+JSAKICB1bmdyb3VwKCkgJT4lCiAgYXJyYW5nZSh0b3BpYywgLWJldGEpICU+JQogIG11dGF0ZSh0ZXJtID0gcmVvcmRlcl93aXRoaW4odGVybSwgYmV0YSwgdG9waWMpKSAlPiUKICBnZ3Bsb3QoYWVzKGJldGEsIHRlcm0sIGZpbGwgPSBmYWN0b3IodG9waWMpKSkgKwogIGdlb21fY29sKHNob3cubGVnZW5kID0gRkFMU0UpICsKICBmYWNldF93cmFwKH4gdG9waWMsIHNjYWxlcz0nZnJlZV95JykgKwogIHNjYWxlX3lfcmVvcmRlcmVkKCkgKwogIHRoZW1lX21pbmltYWwoKSsKICBsYWJzKHRpdGxlID0gIlBhbGFicmFzIG3DoXMgaW1wb3J0YW50ZXMgZW4gOCB0w7NwaWNvcyBTVE0iKSAKYGBgCkVsIG1vZGVsbyBTVE0gY29uIGsgPSA4IHBhcmVjZSB0ZW5lciBtYXlvciDDqXhpdG8gcXVlIExEQSBwYXJhIGRldGVjdGFyIHTDs3BpY29zIGNsYXJvczoKCjEuIFBvbMOtdGljYSBpbnRlcm5hY2lvbmFsCjIuIFBvbMOtdGljYXMgZGUgRWR1Y2FjacOzbiwgU2FsdWQsIERlc2Fycm9sbG8gU29jaWFsCjMuIFBvbGljaWFsZXMgeSBKdXN0aWNpYQo0LiBTb2NpZWRhZCB5IGN1bHR1cmEKNS4gRGVwb3J0ZXMKNi4gUG9sw610aWNhIE5hY2lvbmFsL0VsZWNjaW9uZXMKNy4gRWNvbm9tw61hCjguIENsaW1hIHkgZ2VvZ3JhZsOtYQoKRGFkbyBzdSDDqXhpdG8gaGFzdGEgYWPDoSwgaGFjZW1vcyBsYSBwcnVlYmEgY29uIDEwIHTDs3BpY29zOgpgYGB7ciBzdG1fMTB9CiMgTm8gY29ycmVyCnN0bV8xMCA8LSBzdG0oZG9jdW1lbnRzID0gZGlzY19kZm0sCiAgICAgIEsgPSAxMCwKICAgICAgcHJldmFsZW5jZSA9IH5tZWRpbywKICAgICAgbWF4LmVtLml0cyA9IDc1LCAKICAgICAgZGF0YSA9IG1ldGFkYXRhLAogICAgICBpbml0LnR5cGUgPSAiU3BlY3RyYWwiLAogICAgICBzZWVkID0gMTAxMCkKCndyaXRlX3JkcyhzdG1fMTAsIm1vZGVsb3Mvc3RtXzEwLnJkcyIpCmBgYAoKYGBge3Igc3RtXzEwX3ByZX0Kc3RtXzEwIDwtIHJlYWRfcmRzKCJtb2RlbG9zL3N0bV8xMC5yZHMiKQpgYGAKCmBgYHtyIHN0bV8xMF90ZXJtaW5vc30KdGVybWlub3Nfc3RtXzEwIDwtIHRpZHkoc3RtXzEwLCBtYXRyaXggPSAnYmV0YScpCgogdGVybWlub3Nfc3RtXzEwICU+JQogIGdyb3VwX2J5KHRvcGljKSAlPiUKICBzbGljZV9tYXgoYmV0YSwgbiA9IDE1KSAlPiUgCiAgdW5ncm91cCgpICU+JQogIGFycmFuZ2UodG9waWMsIC1iZXRhKSAlPiUKICBtdXRhdGUodGVybSA9IHJlb3JkZXJfd2l0aGluKHRlcm0sIGJldGEsIHRvcGljKSkgJT4lCiAgZ2dwbG90KGFlcyhiZXRhLCB0ZXJtLCBmaWxsID0gZmFjdG9yKHRvcGljKSkpICsKICBnZW9tX2NvbChzaG93LmxlZ2VuZCA9IEZBTFNFKSArCiAgZmFjZXRfd3JhcCh+IHRvcGljLCBzY2FsZXM9J2ZyZWVfeScpICsKICBzY2FsZV95X3Jlb3JkZXJlZCgpICsKICB0aGVtZV9taW5pbWFsKCkrCiAgbGFicyh0aXRsZSA9ICJQYWxhYnJhcyBtw6FzIGltcG9ydGFudGVzIGVuIDEwIHTDs3BpY29zIFNUTSIpIApgYGAKCk5vdGFtb3MgcXVlIGxvcyB0w7NwaWNvcyA3IHkgMTAgc2UgdnVlbHZlbiBzaW1pbGFyZXMgZW50cmUgc8OtLCBhbWJvcyBwYXJlY2VuIGVzdGFyIHJlbGFjaW9uYWRvcyBhIHBvbMOtdGljYSBpbnRlcm5jaW9uYWwuIEF1bnF1ZSBpbmNvcnBvcmEgdW4gdMOzcGljbyByZWxhY2lvbmFkbyBhIG1lZGljaW5hIHkgc2FsdWQgcHVlZGUgdmVyc2UgcXVlIGxvcyB0w6lybWlub3MgYXNvY2lhZG9zIGEgw6lsIHRpZW5lbiB1biBiZXRhIGJham8sIGRhbmRvIGN1ZW50YSBkZSBsYSBiYWphIHBvdGVuY2lhbGlkYWQgZGUgZXN0YSB0ZW3DoXRpY2EgZW4gZWwgY29udGVuaWRvIGFuYWxpemFkby4KCkRhZG9zIGxvcyByZXN1bHRhZG9zIGhhc3RhIGFjw6EgZGVsIG1vZGVsbyBTVE0sIHZhbW9zIGEgb3B0YXIgcG9yIHF1ZWRhcm5vcyBjb24gdW4gaz04IHlhIHF1ZSBtdWVzdHJhIHTDs3BpY29zIGNvbiBtYXlvciBkZWZpbmljacOzbi4gCkFsIGlndWFsIHF1ZSBlbiBMREEgdmFtb3MgYSB2YWxpZGFyIGVsIHTDs3BpY28gZGUgbWF5b3IgcmVsZXZhbmNpYSBwYXJhIGVzdGUgYW7DoWxpc2lzLCBlbCBuw7ptZXJvIDYgc29icmUgUG9sw610aWNhIE5hY2lvbmFsL0VsZWNjaW9uZXMuIENvbiBsYSBmdW5jacOzbiBsYWJlbFRvcGljcyBpZGVudGlmaWNhbW9zIHF1ZSBsYXMgcGFsYWJyYXMgY29uIG1heW9yIHByb2JhYmlsaWRhZCBkZSBwZXJ0ZW5lY2VyIGFsIHTDs3BpY28gaGFjZW4gY2xhcmEgYWx1c2nDs24gYSBsb3MgcHJpbmNpcGFsZXMgY2FuZGlkYXRvcyBwcmVzaWRlbmNpYWxlcyBtaWVudHJhcyBxdWUgbG9zIHTDqXJtaW5vcyBxdWUgbcOhcyBkaXN0aW5ndWVuIGVsIHRlbWEgc29uIHRhbWJpw6luIGFwZWxsaWRvcyBkZSBjYW5kaWRhdG9zIHBvbMOtdGljb3MuCgpgYGB7ciBsYWJlbF9zdG19CmxhYmVsVG9waWNzKHN0bV84KQpgYGAKCmBgYHtyfQpmaW5kVGhvdWdodHMoc3RtXzgsIAogICAgICAgICAgICAgdGV4dHM9bWV0YWRhdGEgJT4lIHNlbGVjdCh0ZXh0bykgJT4lIHB1bGwoKSwgCiAgICAgICAgICAgICBuPTEwLCAKICAgICAgICAgICAgIHRvcGljcz02KQpgYGAKCkEgY29udGludWFjacOzbiB2YW1vcyBhIGJ1c2NhciBsYSBwcmVkb21pbmFuY2lhIGRlIHTDs3BpY29zIHBvciBkb2N1bWVudG9zCmBgYHtyIGRvY190b3BpY3Nfc3RtfQoKc3RtXzggPC0gc3RtKGRvY3VtZW50cyA9IGRpc2NfZGZtLAogICAgICBLID0gOCwKICAgICAgcHJldmFsZW5jZSA9IH5tZWRpbywKICAgICAgbWF4LmVtLml0cyA9IDc1LCAKICAgICAgZGF0YSA9IG1ldGFkYXRhLAogICAgICBpbml0LnR5cGUgPSAiU3BlY3RyYWwiLAogICAgICBzZWVkID0gODg4KQoKd3JpdGVfcmRzKHN0bV84LCJtb2RlbG9zL3N0bV84LnJkcyIpCmBgYAoKYGBge3Igc3RtXzhfcHJlfQpzdG1fOCA8LSByZWFkX3JkcygibW9kZWxvcy9zdG1fOC5yZHMiKQpgYGAKCkVuIGVzdGUgcGFzbyBub3RhbW9zIHF1ZSBhbCBjcmVhciBsYSBtYXRyaXogdMOzcGljby1kb2N1bWVudG8sIGxhIGZ1bmNpw7NuIHRpZHkgcGlzYSBsb3MgaWQgZGUgY2FkYSBub3RpY2lhLCBnZW5lcmFuZG8gcXVlIGVsIHBvc3RlcmlvciBqb2luIGNvbiBsYSBtZXRhZGF0YSBwYXJhIGlkZW50aWZpY2FyIGVsIG1lZGlvLCBubyBmdW5jaW9uZS4gTm8gcHVkaW1vcyByZXNvbHZlcmxvLiBQb3IgZW5kZSBlbnRlbmRlbW9zIHF1ZSBlbCBhbsOhbGlzaXMgZGUgdMOzcGljb3MgU1RNIHBvciBub3RpY2lhIG5vIGVzIHbDoWxpZG8uCgpgYGB7ciBzdG1fOF9mYWlsfQpzdG1fOCRpZCA8LSBtZXRhZGF0YSRpZApkb2Nfc3RtXzggPC0gdGlkeShzdG1fOCwgbWF0cml4ID0gJ3RoZXRhJykgCnJlc3VsdGFkb3NfY29uX2lkIDwtIG1lcmdlKG1ldGFkYXRhLCBkb2Nfc3RtXzgsIGJ5LnggPSAiaWQiLCBieS55ID0gImRvY3VtZW50IiwgYWxsLnkgPSBUUlVFKQpgYGAKCmBgYHtyIHN0bV84X3RvcGljc19tZWRpb3N9CmRvY19zdG1fOCAlPiUKICByZW5hbWUoaWQgPSBkb2N1bWVudCkgJT4lCiAgbXV0YXRlKHRvcGljID0gY2FzZV93aGVuKAogICAgICAgICAgICAgdG9waWMgPT0gMSB+ICJQb2zDrXRpY2EgSW50ZXJuYWNpb25hbCIsCiAgICAgICAgICAgICB0b3BpYyA9PSAyIH4gIlBvbMOtdGljYXMgZGUgRWR1Y2FjacOzbiwgU2FsdWQsIERlc2Fycm9sbG8gU29jaWFsIiwKICAgICAgICAgICAgIHRvcGljID09IDMgfiAiUG9saWNpYWxlcyB5IEp1c3RpY2lhIiwKICAgICAgICAgICAgIHRvcGljID09IDQgfiAiU29jaWVkYWQgeSBDdWx0dXJhIiwKICAgICAgICAgICAgIHRvcGljID09IDUgfiAiRGVwb3J0ZXMiLAogICAgICAgICAgICAgdG9waWMgPT0gNiB+ICJQb2zDrXRpY2EgTmFjaW9uYWwvRWxlY2Npb25lcyIsCiAgICAgICAgICAgICB0b3BpYyA9PSA3IH4gIkVjb25vbcOtYSIsCiAgICAgICAgICAgICB0b3BpYyA9PSA4IH4gIkNsaW1hIHkgZ2VvZ3JhZsOtYSIpKSAgJT4lIAogIG11dGF0ZShpZCA9IGFzLmludGVnZXIoaWQpKSAlPiUKICBsZWZ0X2pvaW4obWV0YWRhdGEgJT4lICBzZWxlY3QoaWQsIG1lZGlvKSAlPiUgdW5pcXVlKCkpICU+JQogIGdyb3VwX2J5KG1lZGlvLCB0b3BpYykgJT4lCiAgICBzdW1tYXJpc2UobWVhbiA9IG1lYW4oZ2FtbWEpKjEwMCkgJT4lCiAgZ2dwbG90KCkgKwogICAgZ2VvbV9jb2woYWVzKHg9dG9waWMsIHk9bWVhbiwgZmlsbD1tZWRpbyksIHBvc2l0aW9uPSdkb2RnZScpICsKICAgIHRoZW1lX21pbmltYWwoKSsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKSsKICBsYWJzKHRpdGxlID0gIlByZWRvbWluYW5jaWEgZW4gbWVkaW9zIGRlIDggdMOzcGljb3MgU1RNIikgCmBgYAoKUG9kZW1vcyBlbmNvbnRyYXIgcHJlZG9taW5hbmNpYSBkZSB0w7NwaWNvcyBwb3IgbWVkaW86CjEuIFBvbMOtdGljYSBpbnRlcm5hY2lvbmFsID0gUMOhZ2luYSAxMgoyLiBQb2zDrXRpY2FzIGRlIEVkdWNhY2nDs24sIFNhbHVkLCBEZXNhcnJvbGxvIFNvY2lhbCA9IENsYXLDrW4KMy4gUG9saWNpYWxlcyB5IEp1c3RpY2lhID0gQ3LDs25pY2EgeSBQw6FnaW5hIDEyCjQuIFNvY2llZGFkIHkgY3VsdHVyYSA9IFDDoWdpbmEgMTIKNS4gRGVwb3J0ZXMgPSBUw6lsYW0KNi4gUG9sw610aWNhIE5hY2lvbmFsL0VsZWNjaW9uZXMgPSBUw6lsYW0KNy4gRWNvbm9tw61hID0gUMOhZ2luYSAxMgo4LiBDbGltYSB5IGdlb2dyYWbDrWEgPSBUw6lsYW0KCmBgYHtyIGNvbXBhcmFjaW9uX21vZGVsb3N9CnByZWRvbWluYW5jaWFfbGRhIDwtIGRvY190b3BpY3NfbGRhICU+JQogIHJlbmFtZShpZCA9IGRvY3VtZW50KSAlPiUKICBtdXRhdGUodG9waWMgPSBjYXNlX3doZW4oCiAgICAgICAgICAgICB0b3BpYyA9PSAxIH4gIlBvbMOtdGljYSBOYWNpb25hbC9FbGVjY2lvbmVzIiwKICAgICAgICAgICAgIHRvcGljID09IDIgfiAiRGVwb3J0ZXMiLAogICAgICAgICAgICAgdG9waWMgPT0gMyB+ICJTb2NpZWRhZCIsCiAgICAgICAgICAgICB0b3BpYyA9PSA0IH4gIlBvbMOtdGljYSBJbnRlcm5hY2lvbmFsIiwKICAgICAgICAgICAgIHRvcGljID09IDUgfiAiRWNvbm9tw61hIikpICAlPiUgCiAgbXV0YXRlKGlkID0gYXMuaW50ZWdlcihpZCkpICU+JQogIGxlZnRfam9pbihjb3JwdXNfYmFzZSAlPiUgc2VsZWN0KGlkLCBtZWRpbykgJT4lIHVuaXF1ZSgpKSAlPiUKICBncm91cF9ieShtZWRpbywgdG9waWMpICU+JQogIHN1bW1hcmlzZShtZWFuID0gbWVhbihnYW1tYSkgKiAxMDApICU+JQogIGFycmFuZ2UobWVkaW8sIGRlc2MobWVhbikpICU+JQogIHNsaWNlKDEpICU+JQogIG11dGF0ZShtb2RlbCA9ICJMREEiKQoKcHJlZG9taW5hbmNpYV9zdG0gPC0gZG9jX3N0bV84ICU+JQogIHJlbmFtZShpZCA9IGRvY3VtZW50KSAlPiUKICBtdXRhdGUodG9waWMgPSBjYXNlX3doZW4oCiAgICAgICAgICAgICB0b3BpYyA9PSAxIH4gIlBvbMOtdGljYSBJbnRlcm5hY2lvbmFsIiwKICAgICAgICAgICAgIHRvcGljID09IDIgfiAiUG9sw610aWNhcyBkZSBFZHVjYWNpw7NuLCBTYWx1ZCwgRGVzYXJyb2xsbyBTb2NpYWwiLAogICAgICAgICAgICAgdG9waWMgPT0gMyB+ICJQb2xpY2lhbGVzIHkgSnVzdGljaWEiLAogICAgICAgICAgICAgdG9waWMgPT0gNCB+ICJTb2NpZWRhZCB5IEN1bHR1cmEiLAogICAgICAgICAgICAgdG9waWMgPT0gNSB+ICJEZXBvcnRlcyIsCiAgICAgICAgICAgICB0b3BpYyA9PSA2IH4gIlBvbMOtdGljYSBOYWNpb25hbC9FbGVjY2lvbmVzIiwKICAgICAgICAgICAgIHRvcGljID09IDcgfiAiRWNvbm9tw61hIiwKICAgICAgICAgICAgIHRvcGljID09IDggfiAiQ2xpbWEgeSBnZW9ncmFmw61hIikpICAlPiUgCiAgbXV0YXRlKGlkID0gYXMuaW50ZWdlcihpZCkpICU+JQogIGxlZnRfam9pbihjb3JwdXNfYmFzZSAlPiUgc2VsZWN0KGlkLCBtZWRpbykgJT4lIHVuaXF1ZSgpKSAlPiUKICBuYS5vbWl0KG1lZGlvKSAlPiUgCiAgZ3JvdXBfYnkobWVkaW8sIHRvcGljKSAlPiUKICBzdW1tYXJpc2UobWVhbiA9IG1lYW4oZ2FtbWEpICogMTAwKSAlPiUKICBhcnJhbmdlKG1lZGlvLCBkZXNjKG1lYW4pKSAlPiUKICBzbGljZSgxKSAlPiUKICBtdXRhdGUobW9kZWwgPSAiU1RNIikKCiMgQ29tYmluYXIgYW1ib3MgY29uanVudG9zIGRlIGRhdG9zCmNvbWJpbmVkX2RhdGEgPC0gYmluZF9yb3dzKHByZWRvbWluYW5jaWFfbGRhLCBwcmVkb21pbmFuY2lhX3N0bSkKCiMgR3LDoWZpY28gZGUgYmFycmFzIGNvbiBldGlxdWV0YXMgZGUgdMOzcGljb3MKZ2dwbG90KGNvbWJpbmVkX2RhdGEsIGFlcyh4ID0gbWVkaW8sIHkgPSBtZWFuLCBmaWxsID0gbW9kZWwsIGxhYmVsID0gdG9waWMpKSArCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIHBvc2l0aW9uID0gImRvZGdlIikgKwogIGdlb21fdGV4dChwb3NpdGlvbiA9IHBvc2l0aW9uX2RvZGdlKHdpZHRoID0gMSksICAgIAogICAgICAgICAgICB2anVzdCA9IC0wLjUsICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgc2l6ZSA9IDMsICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgIGFlcyhjb2xvciA9IG1vZGVsKSwgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICBhbmdsZSA9IDAsICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgIHNob3cubGVnZW5kID0gRkFMU0UpICsgICAgICAgICAgICAgICAgICAgCiAgdGhlbWVfbWluaW1hbCgpICsKICBjb29yZF9mbGlwKCkrCiAgbGFicyh0aXRsZSA9ICJQcmVkb21pbmFuY2lhIGRlbCB0w7NwaWNvIG3DoXMgcmVsZXZhbnRlIHBvciBtb2RlbG8geSBtZWRpbyIsCiAgICAgICB4ID0gIk1lZGlvIiwKICAgICAgIHkgPSAiUHJlZG9taW5hbmNpYSIsCiAgICAgICBmaWxsID0gIk1vZGVsbyIpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKQoKYGBgCgotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCkEgY29udGludWFjacOzbiwgc2VsZWNjaW9uYXIgbGFzIG5vdGljaWFzIHZpbmN1bGFkYXMgYSBhbGfDum4gdMOzcGljbyByZWxldmFudGUgKHBvciBlamVtcGxvLCDigJxFbGVjY2lvbmVz4oCdKSB5IGNvbnN0cnVpciB1biBjbGFzaWZpY2Fkb3IgcGFyYSBwcmVkZWNpciBsYSBvcmllbnRhY2nDs24gZGVsIGRpYXJpby4gVXRpbGl6YXIgYWxndW5vIGRlIGxvcyBtb2RlbG9zIGRlIGNsYXNpZmljYWNpw7NuIHZpc3RvcyBhIGxvIGxhcmdvIGRlIGFsIERpcGxvbWF0dXJhIChyZWdyZXNpw7NuIGxvZ8Otc3RpY2EsIHJhbmRvbSBmb3Jlc3QsIGV0Yy4pLiBVdGlsaXphciBjb21vIGZlYXR1cmVzIGVsIOKAnFNwYW5pc2ggQmlsbGlvbiBXb3JkIENvcnB1cyBhbmQgRW1iZWRkaW5nc+KAnSwgYW5hbGl6YWRvIGVuIGNsYXNlIChwdWVkZW4gZGVzY2FyZ2FyIGVsIGVtYmVkZGluZyBlbiBmb3JtYXRvIC5iaW4gZGVsIGxpbmspLiDCv1F1w6kgcmVzdWx0YWRvcyBhcnJvamEgZWwgbW9kZWxvPyDCv0VzIHBvc2libGUgbWVkaWFudGUgZWwgdGV4dG8gZGUgbGFzIG5vdGljaWFzIGNvbm9jZXIgbGEgbMOtbmVhIGVkaXRvcmlhbCBkZWwgZGlhcmlvPyBHZW5lcmFyIGxhcyB2aXN1YWxpemFjaW9uZXMgeSB0YWJsYXMgY29ycmVzcG9uZGllbnRlcyBwYXJhIHVuYSBjb3JyZWN0YSBldmFsdWFjacOzbiBkZWwgbW9kZWxvLgoKQnVzY2Ftb3MgbGEgcmVwcmVzZW50YWNpw7NuIHZlY3RvcmlhbCBkZSBwYWxhYnJhcyBlbiBlc3Bhw7FvbDoKYGBge3IgbG9hZF9lbWJlZH0KbG9hZF9lbWJlZGRpbmdzIDwtIGZ1bmN0aW9uKHBhdGg9TlVMTCwgdHlwZT1jKCJ3MnYiLCAiZnQiKSl7CiAgICAgICAgaWYgKHR5cGU9PSJ3MnYiKXsKICAgICAgICAgICAgICAgIGVtYmVkZGluZyA8LSB3b3JkMnZlYzo6cmVhZC53b3JkdmVjdG9ycyhwYXRoLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0eXBlID0gImJpbiIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5vcm1hbGl6ZSA9IFRSVUUpICU+JQogICAgICAgICAgICAgICAgICAgICAgICBhc190aWJibGUocm93bmFtZXM9IndvcmQiKQogICAgICAgIH0KICAgICAgICBlbHNlIGlmICh0eXBlPT0iZnQiKXsKICAgICAgICAgICAgICAgIG1vZGVsIDwtIGZhc3RUZXh0Ujo6ZnRfbG9hZChwYXRoKQogICAgICAgICAgICAgICAgd29yZHMgPC0gZmFzdFRleHRSOjpmdF93b3Jkcyhtb2RlbCkKICAgICAgICAgICAgICAgIGVtYmVkZGluZyA8LSBmYXN0VGV4dFI6OmZ0X3dvcmRfdmVjdG9ycyhtb2RlbCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB3b3JkcykgJT4lCiAgICAgICAgICAgICAgICAgICAgICAgIGFzX3RpYmJsZShyb3duYW1lcz0id29yZCIpCiAgICAgICAgfQogICAgICAgIAogICAgICAgIHJldHVybihlbWJlZGRpbmcpCn0KCgplbWJlZGRpbmcgPC0gbG9hZF9lbWJlZGRpbmdzKHBhdGggPSAiU0JXLXZlY3RvcnMtMzAwLW1pbjUuYmluIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0eXBlID0gIncydiIpCmBgYAoKTm9zIHF1ZWRhbW9zIGNvbiBsYXMgbm90aWNpYXMgY3V5byBtYXlvciBnYW1tYSBlc3TDoSBhc29jaWFkbyBhbCB0w7NwaWNvIDE6CmBgYHtyIG5vdGljaWFzX3RvcGljb18xfQpkb2NzX3RvcGljb18xIDwtIGRvY190b3BpY3NfbGRhICU+JQogICAgICAgICAgICAgIG11dGF0ZShnYW1tYSA9IHJvdW5kKGdhbW1hLCA1KSkgJT4lIAogICAgICAgICAgICAgIHJlbmFtZShpZCA9IGRvY3VtZW50KSAlPiUKICAgICAgICAgICAgICBtdXRhdGUoaWQgPSBhcy5pbnRlZ2VyKGlkKSkgJT4lIAogICAgICAgICAgICAgIGxlZnRfam9pbihjb3JwdXNfYmFzZSAlPiUgc2VsZWN0KGlkLCBtZWRpbywgb3JpZW50YWNpb24pICU+JSB1bmlxdWUoKSkgJT4lCiAgICAgICAgICAgICAgYXJyYW5nZShpZCwgZGVzYyhnYW1tYSkpICU+JSAKICAgICAgICAgICAgICBncm91cF9ieShpZCkgJT4lCiAgICAgICAgICAgICAgc2xpY2VfbWF4KGdhbW1hLCBuID0gMSkgJT4lIAogICAgICAgICAgICAgIGZpbHRlcih0b3BpYyA9PSAxJm9yaWVudGFjaW9uICE9ICduZXV0cm8nKSAlPiUgCiAgICAgICAgICAgICAgbXV0YXRlKG9yaWVudGFjaW9uID0gY2FzZV93aGVuKG9yaWVudGFjaW9uID09ICcrIGNvbnNlcnZhZG9yJ34nY29uc2VydmFkb3InLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvcmllbnRhY2lvbiA9PSAnKyBwcm9ncmVzaXN0YSd+J3Byb2dyZXNpc3RhJykpIApgYGAKCkJ1c2NhbW9zIGxvcyB0b2tlbnMgZGUgbGFzIG5vdGljaWFzIHNlbGVjY2lvbmFkYXM6CmBgYHtyIHRva2Vuc18xfQp3b3Jkc190b3BpY29fMSA8LSBkb2NzX3RvcGljb18xICU+JSAKICAgICAgICAgICAgICAgICAgbGVmdF9qb2luKGNvcnB1c190aWR5X3YyICU+JSBzZWxlY3QoaWQsIHdvcmQpKQpgYGAKClVuaW1vcyBsb3MgdG9rZW5zIGRlbCB0w7NwaWNvIDEgY29uIHN1IGVtYmVkZGluZzoKYGBge3IgZW1iZWRzXzF9CmVtYmVkc190b3BpY29fMSA8LSB3b3Jkc190b3BpY29fMSAlPiUgCiAgICAgICAgICAgICAgICAgICAgbGVmdF9qb2luKGVtYmVkZGluZykKYGBgCgpgYGB7ciBlbWJlZHNfZG9jfQpkb2NzX2VtYmVkIDwtIGVtYmVkc190b3BpY29fMSAlPiUKICAgICAgICBncm91cF9ieShpZCwgb3JpZW50YWNpb24pICU+JQogICAgICAgIHN1bW1hcmlzZShhY3Jvc3MoVjE6VjMwMCwgfm1lYW4oLngsIG5hLnJtPVRSVUUpKSkgJT4lCiAgICAgICAgdW5ncm91cCgpCmBgYAoKYGBge3Igd29ya2Zsb3d9CnNldC5zZWVkKDY0OSkKZG9jc19zcGxpdCA8LSBpbml0aWFsX3NwbGl0KGRvY3NfZW1iZWQsIHN0cmF0YSA9IG9yaWVudGFjaW9uKQoKdHJhaW5fZW1iZWQgPC0gdHJhaW5pbmcoZG9jc19zcGxpdCkKdGVzdF9lbWJlZCA8LSB0ZXN0aW5nKGRvY3Nfc3BsaXQpCgpsYXNzb19zcGVjIDwtIGxvZ2lzdGljX3JlZygKICAgICAgICBwZW5hbHR5ID0gdHVuZSgpLAogICAgICAgIG1peHR1cmUgPSAxKSAlPiUKICAgICAgICBzZXRfbW9kZSgiY2xhc3NpZmljYXRpb24iKSAlPiUKICAgICAgICBzZXRfZW5naW5lKCJnbG1uZXQiKSAKCmRvY3NfZW1iZWRfcmVjaXBlIDwtIHJlY2lwZShvcmllbnRhY2lvbiB+IC4sIGRhdGEgPSB0cmFpbl9lbWJlZCkgJT4lCiAgICAgICAgICAgICAgICAgICAgIHVwZGF0ZV9yb2xlKCJpZCIsIG5ld19yb2xlID0gIklEIikKCgp3Zl9lbWJlZCA8LSB3b3JrZmxvdygpICU+JSAKICAgICAgICBhZGRfcmVjaXBlKGRvY3NfZW1iZWRfcmVjaXBlKSAlPiUKICAgICAgICBhZGRfbW9kZWwobGFzc29fc3BlYykKCmdyaWRfbGFzc28gPC0gZ3JpZF9yZWd1bGFyKHBlbmFsdHkoKSwgbGV2ZWxzID0gMzApCgoKc2V0LnNlZWQoMTIzKQplbWJlZF9mb2xkcyA8LSB2Zm9sZF9jdih0cmFpbl9lbWJlZCwgdiA9IDUpCmBgYAoKRW50cmVuYW1vcyBlbCBtb2RlbG86CmBgYHtyIHRyYWlufQp0dW5lX2xhc3NvX2VtYmVkIDwtIHR1bmVfZ3JpZCgKICAgICAgICB3Zl9lbWJlZCwKICAgICAgICBlbWJlZF9mb2xkcywKICAgICAgICBncmlkID0gZ3JpZF9sYXNzbywKICAgICAgICBjb250cm9sID0gY29udHJvbF9yZXNhbXBsZXMoc2F2ZV9wcmVkID0gVFJVRSkKKQpgYGAKCkVsZWdpbW9zIGxvcyBkb3MgbWVqb3JlcyBtb2RlbG9zIHNlZ8O6biBhY2N1cmFjeSB5IGVycm9yIGVzdMOhbmRhcjoKYGBge3IgYWNjdXJhY3l9CnNob3dfYmVzdCh0dW5lX2xhc3NvX2VtYmVkLCAiYWNjdXJhY3kiLCBuPTIpCmBgYAoKYGBge3Igc3RkX2Vycm9yfQpjaG9zZW5fYXVjX2VtYmVkIDwtIHR1bmVfbGFzc29fZW1iZWQgJT4lCiAgc2VsZWN0X2J5X29uZV9zdGRfZXJyKG1ldHJpYyA9ICJhY2N1cmFjeSIsIC1wZW5hbHR5KQoKY2hvc2VuX2F1Y19lbWJlZApgYGAKCmBgYHtyIGZpbmFsaXplX21vZGVsfQpmaW5hbF9wYXJhbXNfbGFzc29fZW1iZWQgPC0gZmluYWxpemVfd29ya2Zsb3cod2ZfZW1iZWQsIGNob3Nlbl9hdWNfZW1iZWQpCmZpbmFsX3BhcmFtc19sYXNzb19lbWJlZApgYGAKCkZpdGVhbW9zIGVsIG1vZGVsbyBlbGVnaWRvIGNvbiBlbCBzZXQgZGUgdHJhaW46CmBgYHtyIGZpdH0KZml0dGVkX2xhc3NvX2VtYmVkIDwtIGZpdChmaW5hbF9wYXJhbXNfbGFzc29fZW1iZWQsIHRyYWluX2VtYmVkKQpgYGAKClByZWRlY2ltb3Mgc29icmUgZWwgc2V0IGRlIHRlc3Q6CmBgYHtyIHRlc3R9CiAgcHJlZHNfZW1iZWQgPC0gdGVzdF9lbWJlZCAlPiUKICAgICAgICAgIHNlbGVjdChvcmllbnRhY2lvbikgJT4lCiAgICAgICAgICBiaW5kX2NvbHMocHJlZGljdChmaXR0ZWRfbGFzc29fZW1iZWQsIHRlc3RfZW1iZWQsIHR5cGU9InByb2IiKSkgJT4lCiAgICAgICAgICBiaW5kX2NvbHMocHJlZGljdChmaXR0ZWRfbGFzc29fZW1iZWQsIHRlc3RfZW1iZWQsIHR5cGU9ImNsYXNzIikpICU+JSAKICBtdXRhdGUob3JpZW50YWNpb24gPSBmYWN0b3Iob3JpZW50YWNpb24sbGV2ZWxzID0gYygiY29uc2VydmFkb3IiLCJwcm9ncmVzaXN0YSIpKSwKICAgICAgICAgLnByZWRfY2xhc3MgPSBmYWN0b3IoLnByZWRfY2xhc3MsbGV2ZWxzID0gYygiY29uc2VydmFkb3IiLCJwcm9ncmVzaXN0YSIpKSkgCiAgICAgICAKYGBgCgpgYGB7ciBtYXRyaXogY29uZnVzaW9ufQpjbSA8LSBjb25mX21hdChkYXRhID0gcHJlZHNfZW1iZWQsCiAgICAgICAgICAgICAgIHRydXRoID0gb3JpZW50YWNpb24sCiAgICAgICAgICAgICAgIGVzdGltYXRlID0gLnByZWRfY2xhc3MgKQoKY20KYGBgCgoKYGBge3IgbWV0cmljYX0KY2xhc3NfbWV0cmljcyA8LSBtZXRyaWNfc2V0KHByZWNpc2lvbiwgYWNjdXJhY3ksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICByZWNhbGwsIGZfbWVhcykKY2xhc3NfbWV0cmljcyhwcmVkc19lbWJlZCwgdHJ1dGggPSBvcmllbnRhY2lvbiwgZXN0aW1hdGUgPSAucHJlZF9jbGFzcykgCmBgYApBY8OhIGludGVyY2FtYmlhbW9zIGVsIG5pdmVsIGRlIGxhcyBjbGFzZXMgcGFyYSBpZGVudGlmaWNhciBlbCByZWNhbGwgZGUgbGEgY2xhc2UgcHJvZ3Jlc2lzdGEKYGBge3IgcHJlZHN9CiAgcHJlZHNfZW1iZWRfMiA8LSB0ZXN0X2VtYmVkICU+JQogICAgICAgICAgc2VsZWN0KG9yaWVudGFjaW9uKSAlPiUKICAgICAgICAgIGJpbmRfY29scyhwcmVkaWN0KGZpdHRlZF9sYXNzb19lbWJlZCwgdGVzdF9lbWJlZCwgdHlwZT0icHJvYiIpKSAlPiUKICAgICAgICAgIGJpbmRfY29scyhwcmVkaWN0KGZpdHRlZF9sYXNzb19lbWJlZCwgdGVzdF9lbWJlZCwgdHlwZT0iY2xhc3MiKSkgJT4lIAogIG11dGF0ZShvcmllbnRhY2lvbiA9IGZhY3RvcihvcmllbnRhY2lvbixsZXZlbHMgPSBjKCJwcm9ncmVzaXN0YSIsImNvbnNlcnZhZG9yIikpLAogICAgICAgICAucHJlZF9jbGFzcyA9IGZhY3RvcigucHJlZF9jbGFzcyxsZXZlbHMgPSBjKCJwcm9ncmVzaXN0YSIsImNvbnNlcnZhZG9yIikpKSAKCmNsYXNzX21ldHJpY3MocHJlZHNfZW1iZWRfMiwgdHJ1dGggPSBvcmllbnRhY2lvbiwgZXN0aW1hdGUgPSAucHJlZF9jbGFzcykgCiAgICAgICAKYGBgCgo2OCUgZGUgbG9zIGNhc29zIHNvbiBjbGFzaWZpY2Fkb3MgY29ycmVjdGFtZW50ZSBjb21vIGNvbnNlcnZhZG9yZXMgbyBwcm9ncmVzaXN0YXMuCkVuIGxvcyBjYXNvcyBkZSBtZWRpb3MgY29uc2VydmFkb3JlcywgZW4gdW4gODYlIGRlIGxhcyB2ZWNlcyBlbCBjb250ZW5pZG8gZGUgbGFzIG5vdGljaWFzIHNpcnZlIHBhcmEgaWRlbnRpZmljYXJsb3MgY29tbyB0YWxlcy4gRW4gY29udHJhc3RlIHNvbG8gZWwgMzglIGRlIGxvcyBkb2N1bWVudG9zIGRlIG1lZGlvcyBjb24gb3JpZW50YWNpw7NuIHByb2dyZXNpc3RhIHB1ZWRlbiBzZXIgaWRlbnRpZmljYWRvcyBjb21vIHRhbGVzIGEgcGFydGlyIGRlIHN1IGNvbnRlbmlkby4KCg==